Basic service entity completed and tested.
added UML to guide actual implementation for Host. ModuleLoader, ServiceGateway, are written but untested.
This commit is contained in:
		
							
								
								
									
										12
									
								
								src/GameServiceWarden.Host/GameServiceWarden.Host.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/GameServiceWarden.Host/GameServiceWarden.Host.csproj
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |  | ||||||
|  |   <ItemGroup> | ||||||
|  |     <ProjectReference Include="..\GameServiceWarden.ModuleAPI\GameServiceWarden.ModuleAPI.csproj" /> | ||||||
|  |   </ItemGroup> | ||||||
|  |  | ||||||
|  |   <PropertyGroup> | ||||||
|  |     <OutputType>Exe</OutputType> | ||||||
|  |     <TargetFramework>netcoreapp3.1</TargetFramework> | ||||||
|  |   </PropertyGroup> | ||||||
|  |  | ||||||
|  | </Project> | ||||||
							
								
								
									
										14
									
								
								src/GameServiceWarden.Host/Logging/FileLogReceiver.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/GameServiceWarden.Host/Logging/FileLogReceiver.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | using System; | ||||||
|  |  | ||||||
|  | namespace GameServiceWarden.Host.Logging | ||||||
|  | { | ||||||
|  |     public class FileLogReceiver : ILogReceiver | ||||||
|  |     { | ||||||
|  |         public LogLevel Level => LogLevel.INFO; | ||||||
|  |  | ||||||
|  |         public void LogMessage(string message, DateTime time, LogLevel level) | ||||||
|  |         { | ||||||
|  |              | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								src/GameServiceWarden.Host/Logging/ILogRecievable.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/GameServiceWarden.Host/Logging/ILogRecievable.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | using System; | ||||||
|  |  | ||||||
|  | namespace GameServiceWarden.Host.Logging | ||||||
|  | { | ||||||
|  |     public interface ILogReceiver | ||||||
|  |     { | ||||||
|  |         /// <summary> | ||||||
|  |         /// The severity of the messages this log should receive. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <value>The severity of the logs.</value> | ||||||
|  |         LogLevel Level { get; } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Logs the message. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="message">The message to be logged.</param> | ||||||
|  |         /// <param name="time">The time at which this message was requested to be logged.</param> | ||||||
|  |         /// <param name="level">The severity of this message.</param> | ||||||
|  |         void LogMessage(string message, DateTime time, LogLevel level); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										10
									
								
								src/GameServiceWarden.Host/Logging/LogLevel.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/GameServiceWarden.Host/Logging/LogLevel.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | namespace GameServiceWarden.Host.Logging | ||||||
|  | { | ||||||
|  |     public enum LogLevel | ||||||
|  |     { | ||||||
|  |         FATAL, | ||||||
|  |         INFO, | ||||||
|  |         WARNING, | ||||||
|  |         DEBUG, | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										46
									
								
								src/GameServiceWarden.Host/Logging/Logger.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/GameServiceWarden.Host/Logging/Logger.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  |  | ||||||
|  | namespace GameServiceWarden.Host.Logging | ||||||
|  | { | ||||||
|  |     public class Logger { | ||||||
|  |         private HashSet<ILogReceiver> listeners = new HashSet<ILogReceiver>(); | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Logs the message to listeners that are listening to the set severity of the message or greater. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="message">The message to log.</param> | ||||||
|  |         /// <param name="level">The level of severity, by default, info.</param> | ||||||
|  |         public void Log(string message, LogLevel level = LogLevel.INFO) { | ||||||
|  |             foreach (ILogReceiver listener in listeners) | ||||||
|  |             { | ||||||
|  |                 if (level <= listener.Level) { | ||||||
|  |                     listener.LogMessage(message, DateTime.Now, level); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Adds a log listener. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="listener">The listener to add.</param> | ||||||
|  |         public void AddLogListener(ILogReceiver listener) { | ||||||
|  |             listeners.Add(listener); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Removes a log listener. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="listener">The listener to remove.</param> | ||||||
|  |         public void RemoveLogListener(ILogReceiver listener) { | ||||||
|  |             listeners.Remove(listener); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Called when all listeners should perform any flushing they need. | ||||||
|  |         /// </summary> | ||||||
|  |         public static void FlushListeners() { | ||||||
|  |  | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,13 @@ | |||||||
|  | namespace GameServiceWarden.Host.Modules.Exceptions | ||||||
|  | { | ||||||
|  |     [System.Serializable] | ||||||
|  |     public class CorruptedServiceInfoException : System.Exception | ||||||
|  |     { | ||||||
|  |         public CorruptedServiceInfoException() { } | ||||||
|  |         public CorruptedServiceInfoException(string message) : base(message) { } | ||||||
|  |         public CorruptedServiceInfoException(string message, System.Exception inner) : base(message, inner) { } | ||||||
|  |         protected CorruptedServiceInfoException( | ||||||
|  |             System.Runtime.Serialization.SerializationInfo info, | ||||||
|  |             System.Runtime.Serialization.StreamingContext context) : base(info, context) { } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,17 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Reflection; | ||||||
|  | using System.Runtime.Serialization; | ||||||
|  |  | ||||||
|  | namespace GameServiceWarden.Host.Modules.Exceptions | ||||||
|  | { | ||||||
|  |     [Serializable] | ||||||
|  |     public class NoServiceableFoundException : Exception | ||||||
|  |     { | ||||||
|  |         public NoServiceableFoundException(string message) : base(message) { } | ||||||
|  |         public NoServiceableFoundException(string message, Exception inner) : base(message, inner) { } | ||||||
|  |         protected NoServiceableFoundException( | ||||||
|  |             SerializationInfo info, | ||||||
|  |             StreamingContext context) : base(info, context) { } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,15 @@ | |||||||
|  | using System; | ||||||
|  |  | ||||||
|  | namespace GameServiceWarden.Host.Modules.Exceptions | ||||||
|  | { | ||||||
|  |     [System.Serializable] | ||||||
|  |     public class NotServiceableTypeException : Exception | ||||||
|  |     { | ||||||
|  |         public NotServiceableTypeException() { } | ||||||
|  |         public NotServiceableTypeException(string message) : base(message) { } | ||||||
|  |         public NotServiceableTypeException(string message, System.Exception inner) : base(message, inner) { } | ||||||
|  |         protected NotServiceableTypeException( | ||||||
|  |             System.Runtime.Serialization.SerializationInfo info, | ||||||
|  |             System.Runtime.Serialization.StreamingContext context) : base(info, context) { } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										33
									
								
								src/GameServiceWarden.Host/Modules/ModuleLoadContext.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/GameServiceWarden.Host/Modules/ModuleLoadContext.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | using System; | ||||||
|  | using System.Reflection; | ||||||
|  | using System.Runtime.Loader; | ||||||
|  |  | ||||||
|  | namespace GameServiceWarden.Host.Modules | ||||||
|  | { | ||||||
|  |     class ModuleLoadContext : AssemblyLoadContext | ||||||
|  |     { | ||||||
|  |         private AssemblyDependencyResolver dependencyResolver; | ||||||
|  |  | ||||||
|  |         public ModuleLoadContext(string path) { | ||||||
|  |             dependencyResolver = new AssemblyDependencyResolver(path); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override Assembly Load(AssemblyName assemblyName) | ||||||
|  |         { | ||||||
|  |             string assemblyPath = dependencyResolver.ResolveAssemblyToPath(assemblyName); | ||||||
|  |             if (assemblyPath != null) { | ||||||
|  |                 return LoadFromAssemblyPath(assemblyPath); | ||||||
|  |             } | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) | ||||||
|  |         { | ||||||
|  |             String libraryPath = dependencyResolver.ResolveUnmanagedDllToPath(unmanagedDllName); | ||||||
|  |             if (libraryPath != null) { | ||||||
|  |                 return LoadUnmanagedDllFromPath(libraryPath); | ||||||
|  |             } | ||||||
|  |             return IntPtr.Zero; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										84
									
								
								src/GameServiceWarden.Host/Modules/ModuleLoader.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								src/GameServiceWarden.Host/Modules/ModuleLoader.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Reflection; | ||||||
|  | using GameServiceWarden.Host.Modules.Exceptions; | ||||||
|  | using GameServiceWarden.ModuleAPI; | ||||||
|  |  | ||||||
|  | namespace GameServiceWarden.Host.Modules | ||||||
|  | { | ||||||
|  |     public class ModuleLoader //Gateway | ||||||
|  |     { | ||||||
|  |         /// <summary> | ||||||
|  |         /// Loads an extension module. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="path">The path to the module.</param> | ||||||
|  |         /// <returns>An </<see cref="IEnumerable{IGameServiceModule}"/> from the given module.</returns> | ||||||
|  |         /// <exception cref="NoServiceableFoundException">When the module requested to be loaded does not contain any public <see cref="IGameServiceable"/> classes.</exception> | ||||||
|  |         public IEnumerable<IGameServiceModule> LoadModules(string path) | ||||||
|  |         { | ||||||
|  |             return instantiateServiceable(loadAssembly(path)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Loads all module for each given path to modules file. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="paths">The paths to load modules for.</param> | ||||||
|  |         /// <returns>A <see cref="Dictionary{string, IEnumerable{IGameServiceModule}}"/> where the key is a <see cref="string"/> that is the associated path.</returns> | ||||||
|  |         public Dictionary<string, IEnumerable<IGameServiceModule>> LoadAllModules(IEnumerable<string> paths) | ||||||
|  |         { | ||||||
|  |             Dictionary<string, IEnumerable<IGameServiceModule>> res = new Dictionary<string, IEnumerable<IGameServiceModule>>(); | ||||||
|  |             foreach (string path in paths) | ||||||
|  |             { | ||||||
|  |                 res.Add(path, LoadModules(path)); | ||||||
|  |             } | ||||||
|  |             return res; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Loads all module for each given path to modules file. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="paths">The paths to load modules for.</param> | ||||||
|  |         /// <returns>A <see cref="Dictionary{string, IEnumerable{IGameServiceModule}}"/> where the key is a <see cref="string"/> that is the associated path.</returns> | ||||||
|  |         public Dictionary<string, IEnumerable<IGameServiceModule>> LoadAllModules(params string[] paths) | ||||||
|  |         { | ||||||
|  |             return LoadAllModules(paths); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private Assembly loadAssembly(string path) | ||||||
|  |         { | ||||||
|  |             ModuleLoadContext moduleLoadContext = new ModuleLoadContext(path); | ||||||
|  |             return moduleLoadContext.LoadFromAssemblyPath(path); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private IEnumerable<IGameServiceModule> instantiateServiceable(Assembly assembly) | ||||||
|  |         { | ||||||
|  |             int serviceableCount = 0; | ||||||
|  |             foreach (Type type in assembly.GetExportedTypes()) | ||||||
|  |             { | ||||||
|  |                 if (typeof(IGameServiceModule).IsAssignableFrom(type)) | ||||||
|  |                 { | ||||||
|  |                     IGameServiceModule res = Activator.CreateInstance(type) as IGameServiceModule; | ||||||
|  |                     if (res != null) | ||||||
|  |                     { | ||||||
|  |                         serviceableCount++; | ||||||
|  |                         yield return res; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (serviceableCount == 0) | ||||||
|  |             { | ||||||
|  |                 List<string> typeNames = new List<string>(); | ||||||
|  |                 foreach (Type type in assembly.GetExportedTypes()) | ||||||
|  |                 { | ||||||
|  |                     typeNames.Add(type.FullName); | ||||||
|  |                 } | ||||||
|  |                 string types = String.Join(',', typeNames); | ||||||
|  |  | ||||||
|  |                 throw new NoServiceableFoundException( | ||||||
|  |                     $"No public classes in {assembly} from {assembly.Location} implemented {typeof(IGameService).FullName}." + | ||||||
|  |                     $"Detected types: {types}"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										72
									
								
								src/GameServiceWarden.Host/Modules/ServiceGateway.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/GameServiceWarden.Host/Modules/ServiceGateway.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | |||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.IO; | ||||||
|  | using GameServiceWarden.Host.Modules.Exceptions; | ||||||
|  |  | ||||||
|  | namespace GameServiceWarden.Host.Modules | ||||||
|  | { | ||||||
|  |     public class ServiceGateway | ||||||
|  |     { | ||||||
|  |         private string dataDirectory; | ||||||
|  |         private const string SERVICE_NAME = "Service Name"; | ||||||
|  |         private const string ASSEMBLY_NAME = "Assembly Name"; | ||||||
|  |         private const string MODULE_NAME = "Module Name"; | ||||||
|  |         private const string EXTENSION = ".sin"; //Service info | ||||||
|  |  | ||||||
|  |         public ServiceGateway(string dataDirectory) | ||||||
|  |         { | ||||||
|  |             if (!Directory.Exists(dataDirectory)) Directory.CreateDirectory(dataDirectory); | ||||||
|  |             this.dataDirectory = dataDirectory; | ||||||
|  |         } | ||||||
|  |         public void SaveService(string serviceName, string assemblyName, string moduleName) | ||||||
|  |         { | ||||||
|  |             string serviceInfoPath = dataDirectory + Path.DirectorySeparatorChar + serviceName + EXTENSION; | ||||||
|  |             using (StreamWriter writer = File.CreateText(serviceInfoPath)) | ||||||
|  |             { | ||||||
|  |                 writer.WriteLine($"{SERVICE_NAME} : {serviceName}"); | ||||||
|  |                 writer.WriteLine($"{ASSEMBLY_NAME} : {assemblyName}"); | ||||||
|  |                 writer.WriteLine($"{MODULE_NAME} : {moduleName}"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public string GetServiceName(string path) | ||||||
|  |         { | ||||||
|  |             return GetServiceInfoValue(path, SERVICE_NAME); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public string GetServiceModuleName(string path) | ||||||
|  |         { | ||||||
|  |             return GetServiceInfoValue(path, MODULE_NAME); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public string GetServiceAssemblyName(string path) | ||||||
|  |         { | ||||||
|  |             return GetServiceInfoValue(path, ASSEMBLY_NAME); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private string GetServiceInfoValue(string path, string key) | ||||||
|  |         { | ||||||
|  |             IEnumerable<string> lines = File.ReadAllLines(path); | ||||||
|  |             foreach (string line in lines) | ||||||
|  |             { | ||||||
|  |                 if (line.StartsWith($"{key}: ")) | ||||||
|  |                 { | ||||||
|  |                     return line.Substring(key.Length + 2); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             throw new CorruptedServiceInfoException($"\"{path}\" is corrupted. Could not find value for: {key}."); | ||||||
|  |  | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public IEnumerable<string> GetAllServiceInfoPaths() | ||||||
|  |         { | ||||||
|  |             string[] files = Directory.GetFiles(dataDirectory); | ||||||
|  |             foreach (string filePath in files) | ||||||
|  |             { | ||||||
|  |                 if (Path.GetExtension(filePath).Equals(EXTENSION)) | ||||||
|  |                 { | ||||||
|  |                     yield return filePath; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										150
									
								
								src/GameServiceWarden.Host/Modules/ServiceInfo.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								src/GameServiceWarden.Host/Modules/ServiceInfo.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,150 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Collections.ObjectModel; | ||||||
|  | using System.Globalization; | ||||||
|  | using System.IO; | ||||||
|  | using System.Threading; | ||||||
|  | using GameServiceWarden.Host.Preferences; | ||||||
|  | using GameServiceWarden.ModuleAPI; | ||||||
|  |  | ||||||
|  | namespace GameServiceWarden.Host.Modules | ||||||
|  | { | ||||||
|  |     public class ServiceInfo : IDisposable //entity | ||||||
|  |     { | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// The name of the service itself, independent of the name of the module this service is using. | ||||||
|  |         /// </summary> | ||||||
|  |         public string ServiceName { get { return serviceName; } set { Interlocked.Exchange(ref serviceName, value); } } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// The services console output stream. | ||||||
|  |         /// </summary> | ||||||
|  |         private volatile string serviceName; //thread-safe(?) | ||||||
|  |         public Stream ServiceConsoleStream { get; private set; } // Thread safe. | ||||||
|  |         private object controlLock = new object(); | ||||||
|  |         private volatile ServiceState state; | ||||||
|  |         private readonly IGameService service; | ||||||
|  |         private readonly string assemblyName; | ||||||
|  |         private readonly string moduleName; | ||||||
|  |         private readonly Dictionary<string, IConfigurable> configurables = new Dictionary<string, IConfigurable>(); | ||||||
|  |         private bool disposed; | ||||||
|  |  | ||||||
|  |         public ServiceInfo(IGameService service, string moduleName, string assemblyName) | ||||||
|  |         { | ||||||
|  |             this.service = service ?? throw new ArgumentNullException("serviceable"); | ||||||
|  |             this.moduleName = moduleName ?? throw new ArgumentNullException("moduleName"); | ||||||
|  |             this.assemblyName = assemblyName ?? throw new ArgumentNullException("assemblyName"); | ||||||
|  |             this.service.StateChangeEvent += OnServiceStateChange; | ||||||
|  |  | ||||||
|  |             foreach (IConfigurable configurable in service.Configurables) | ||||||
|  |             { | ||||||
|  |                 configurables.Add(configurable.OptionName, configurable); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Starts this service. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <exception cref="InvalidOperationException">Is thrown when the service is already running.</exception> | ||||||
|  |         public void Start() | ||||||
|  |         { | ||||||
|  |             lock (controlLock) | ||||||
|  |             { | ||||||
|  |                 if (state != ServiceState.Stopped) throw new InvalidOperationException("Service instance already running."); | ||||||
|  |                 DateTimeFormatInfo format = new DateTimeFormatInfo(); | ||||||
|  |                 format.TimeSeparator = "-"; | ||||||
|  |                 format.DateSeparator = "_"; | ||||||
|  |                 ServiceConsoleStream = new MemoryStream(8 * 1024 * 1024); // 8 MB | ||||||
|  |                 ServiceConsoleStream = Stream.Synchronized(ServiceConsoleStream); | ||||||
|  |                 service.InitializeService(new StreamWriter(ServiceConsoleStream)); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Stops the service. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <exception cref="InvalidOperationException">Is thrown when the is not running.</exception> | ||||||
|  |         public void Stop() | ||||||
|  |         { | ||||||
|  |             lock (controlLock) | ||||||
|  |             { | ||||||
|  |                 if (state != ServiceState.Running) throw new InvalidOperationException("Service instance not running."); | ||||||
|  |                 this.service.ElegantShutdown(); | ||||||
|  |                 ServiceConsoleStream.Close(); | ||||||
|  |                 ServiceConsoleStream = null; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Sends a command to this service to execute. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="command">The command to execute.</param> | ||||||
|  |         /// <exception cref="InvalidOperationException">Is thrown when the service is not running.</exception> | ||||||
|  |         public void ExecuteCommand(string command) | ||||||
|  |         { | ||||||
|  |             lock (controlLock) | ||||||
|  |             { | ||||||
|  |                 if (state != ServiceState.Running) throw new InvalidOperationException("Service instance not running."); | ||||||
|  |                 service.ExecuteCommand(command); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets the possible <see cref="IConfigurable"/>'s for this service. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <returns>A <see cref="IReadOnlyDictionary{string, IConfigurable}"/> is returned where the string is the option name and the configurable is what handles actually changing the values.</returns> | ||||||
|  |         public IReadOnlyDictionary<string, IConfigurable> GetConfigurables() | ||||||
|  |         { | ||||||
|  |             return new ReadOnlyDictionary<string, IConfigurable>(this.configurables); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <returns>The <see cref="ServiceState"/> that this service is currently in.</returns> | ||||||
|  |         public ServiceState GetServiceState() | ||||||
|  |         { | ||||||
|  |             lock (controlLock) | ||||||
|  |             { | ||||||
|  |                 return state; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <returns>The name of the module this service uses.</returns> | ||||||
|  |         public string GetModuleName() | ||||||
|  |         { | ||||||
|  |             return moduleName; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <returns>The name of assembly this module is contained in.</returns> | ||||||
|  |         public string GetAssemblyName() | ||||||
|  |         { | ||||||
|  |             return assemblyName; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void OnServiceStateChange(object sender, ServiceState current) | ||||||
|  |         { | ||||||
|  |             lock (controlLock) | ||||||
|  |             { | ||||||
|  |                 this.state = current; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected virtual void Dispose(bool disposing) | ||||||
|  |         { | ||||||
|  |             if (!disposed) | ||||||
|  |             { | ||||||
|  |                 if (disposing) | ||||||
|  |                 { | ||||||
|  |                     ServiceConsoleStream?.Dispose(); | ||||||
|  |                 } | ||||||
|  |                 //No unmanaged code, therefore, no finalizer. | ||||||
|  |                 disposed = true; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public void Dispose() | ||||||
|  |         { | ||||||
|  |             Dispose(disposing: true); | ||||||
|  |             GC.SuppressFinalize(this); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										80
									
								
								src/GameServiceWarden.Host/Modules/ServiceManager.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/GameServiceWarden.Host/Modules/ServiceManager.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.IO; | ||||||
|  | using GameServiceWarden.ModuleAPI; | ||||||
|  |  | ||||||
|  | namespace GameServiceWarden.Host.Modules | ||||||
|  | { | ||||||
|  |     public class ServiceManager | ||||||
|  |     { | ||||||
|  |         private Dictionary<string, ServiceInfo> services = new Dictionary<string, ServiceInfo>(); | ||||||
|  |         private Dictionary<string, Dictionary<string, IGameServiceModule>> modules = new Dictionary<string, Dictionary<string, IGameServiceModule>>(); | ||||||
|  |  | ||||||
|  |         public void AddModule(string assemblyName, IGameServiceModule module) | ||||||
|  |         { | ||||||
|  |             if (!modules.ContainsKey(assemblyName)) modules.Add(assemblyName, new Dictionary<string, IGameServiceModule>()); | ||||||
|  |             modules[assemblyName][module.Name] = module; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public void RemoveModule(string assemblyName, string moduleName) | ||||||
|  |         { | ||||||
|  |             if (!modules.ContainsKey(assemblyName) || !modules[assemblyName].ContainsKey(moduleName)) throw new KeyNotFoundException($"No module registered from {assemblyName} named {moduleName}."); | ||||||
|  |             modules[assemblyName].Remove(moduleName); | ||||||
|  |             if (modules[assemblyName].Count == 0) modules.Remove(assemblyName); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public void CreateService(string serviceName, string assemblyName, string moduleName) | ||||||
|  |         { | ||||||
|  |             if (!modules.ContainsKey(assemblyName) || modules[assemblyName].ContainsKey(moduleName)) throw new KeyNotFoundException($"No module registered from \"{assemblyName}\" named \"{moduleName}\"."); | ||||||
|  |             if (services.ContainsKey(serviceName)) throw new ArgumentException($"Service of Name \"{serviceName}\" already exists."); | ||||||
|  |  | ||||||
|  |             services.Add(serviceName, new ServiceInfo(modules[assemblyName][moduleName].CreateGameService(), moduleName, assemblyName)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public IReadOnlyCollection<string> GetServiceNames() | ||||||
|  |         { | ||||||
|  |             string[] names = new string[services.Count]; | ||||||
|  |             services.Keys.CopyTo(names, 0); | ||||||
|  |             return names; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public IEnumerable<string> GetServiceOptions(string serviceName) | ||||||
|  |         { | ||||||
|  |             if (!services.ContainsKey(serviceName)) throw new KeyNotFoundException($"Service under name \"{serviceName}\" not found."); | ||||||
|  |             ServiceInfo serviceInfo = services[serviceName]; | ||||||
|  |             return serviceInfo.GetConfigurables().Keys; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public bool SetServiceOptionValue(string serviceName, string optionName, string value) | ||||||
|  |         { | ||||||
|  |             if (!services.ContainsKey(serviceName)) throw new KeyNotFoundException($"Service under name \"{serviceName}\" not found."); | ||||||
|  |             if (!services[serviceName].GetConfigurables().ContainsKey(optionName)) throw new KeyNotFoundException($"Option \"{optionName}\" for service \"{serviceName}\" not found."); | ||||||
|  |             IConfigurable configurable = services[serviceName].GetConfigurables()[optionName]; | ||||||
|  |             return configurable.SetValue(value); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public void StartService(string serviceName) | ||||||
|  |         { | ||||||
|  |             if (!services.ContainsKey(serviceName)) throw new KeyNotFoundException($"Service under name \"{serviceName}\" not found."); | ||||||
|  |             services[serviceName].Start(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public void StopService(string serviceName) | ||||||
|  |         { | ||||||
|  |             if (!services.ContainsKey(serviceName)) throw new KeyNotFoundException($"Service under name \"{serviceName}\" not found."); | ||||||
|  |             services[serviceName].Stop(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public void ExecuteCommand(string serviceName, string command) | ||||||
|  |         { | ||||||
|  |             if (!services.ContainsKey(serviceName)) throw new KeyNotFoundException($"Service under name \"{serviceName}\" not found."); | ||||||
|  |             services[serviceName].ExecuteCommand(command); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Stream GetServiceConsoleStream(string serviceName) | ||||||
|  |         { | ||||||
|  |             if (!services.ContainsKey(serviceName)) throw new KeyNotFoundException($"Service under name \"{serviceName}\" not found."); | ||||||
|  |             return services[serviceName].ServiceConsoleStream; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										46
									
								
								src/GameServiceWarden.Host/Preferences/GeneralPreferences.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/GameServiceWarden.Host/Preferences/GeneralPreferences.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | using System; | ||||||
|  | using System.IO; | ||||||
|  | using System.Net; | ||||||
|  | using System.Reflection; | ||||||
|  | using System.Xml.Serialization; | ||||||
|  |  | ||||||
|  | namespace GameServiceWarden.Host.Preferences | ||||||
|  | { | ||||||
|  |     [Serializable] | ||||||
|  |     public class GeneralPreferences : IPersistable | ||||||
|  |     { | ||||||
|  |         //XML serialization invariants. | ||||||
|  |         private readonly XmlSerializer xmlSerializer; | ||||||
|  |         private readonly string APP_DATA_DIR; | ||||||
|  |  | ||||||
|  |         //Preferences stored. | ||||||
|  |         public int Port = 8080; | ||||||
|  |         public string ListeningIP = IPAddress.Any.ToString(); | ||||||
|  |         public string ModuleDataPath; | ||||||
|  |  | ||||||
|  |         public GeneralPreferences() | ||||||
|  |         { | ||||||
|  |             APP_DATA_DIR = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "/" + Assembly.GetAssembly(GetType()).GetName().Name + "/"; | ||||||
|  |             xmlSerializer = new XmlSerializer(GetType()); | ||||||
|  |  | ||||||
|  |             this.ModuleDataPath = APP_DATA_DIR + "modules/"; | ||||||
|  |             Load(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public void Save() | ||||||
|  |         { | ||||||
|  |             using (FileStream writer = new FileStream(APP_DATA_DIR + GetType().Name + ".xml", FileMode.OpenOrCreate)) | ||||||
|  |             { | ||||||
|  |                 xmlSerializer.Serialize(writer, this); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         public void Load() | ||||||
|  |         { | ||||||
|  |             using (FileStream reader = new FileStream(APP_DATA_DIR + GetType().Name + ".xml", FileMode.Open)) | ||||||
|  |             { | ||||||
|  |                 xmlSerializer.Deserialize(reader); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										8
									
								
								src/GameServiceWarden.Host/Preferences/IPersistable.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/GameServiceWarden.Host/Preferences/IPersistable.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | namespace GameServiceWarden.Host.Preferences | ||||||
|  | { | ||||||
|  |     public interface IPersistable | ||||||
|  |     { | ||||||
|  |         public void Save(); | ||||||
|  |         public void Load(); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								src/GameServiceWarden.Host/Program.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/GameServiceWarden.Host/Program.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | using System; | ||||||
|  |  | ||||||
|  | namespace GameServiceWarden.Host | ||||||
|  | { | ||||||
|  |     class Program | ||||||
|  |     { | ||||||
|  |         static void Main(string[] args) | ||||||
|  |         { | ||||||
|  |             Console.WriteLine("Hello World!"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										58
									
								
								src/GameServiceWarden.Host/UMLSketch.drawio
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/GameServiceWarden.Host/UMLSketch.drawio
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | |||||||
|  | <mxfile host="65bd71144e" modified="2020-12-24T22:32:37.030Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Code/1.52.1 Chrome/83.0.4103.122 Electron/9.3.5 Safari/537.36" etag="XRoGvs6ajEFQR45Yu_qq" version="13.10.0" type="embed"> | ||||||
|  |     <diagram id="LHR7ubqCPd17_LyHkaH9" name="Structure"> | ||||||
|  |         <mxGraphModel dx="1009" dy="418" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0"> | ||||||
|  |             <root> | ||||||
|  |                 <mxCell id="0"/> | ||||||
|  |                 <mxCell id="1" parent="0"/> | ||||||
|  |                 <mxCell id="v9q6W0nyI9kZyF3peKlB-5" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;jumpStyle=none;dashed=1;endArrow=block;endFill=0;exitX=1;exitY=0;exitDx=0;exitDy=0;" parent="1" source="v9q6W0nyI9kZyF3peKlB-1" target="v9q6W0nyI9kZyF3peKlB-4" edge="1"> | ||||||
|  |                     <mxGeometry relative="1" as="geometry"/> | ||||||
|  |                 </mxCell> | ||||||
|  |                 <mxCell id="v9q6W0nyI9kZyF3peKlB-1" value="<table border="1" width="100%" cellpadding="4" style="width: 100% ; height: 100% ; border-collapse: collapse"><tbody><tr><th align="center">ServiceInfo (entity)</th></tr><tr><td align="center">- serviceName: string<br>- controlLock: object<br>- state: ServiceState<br>- service: IGameService<br>- serviceConsoleStream: Stream<br>- moduleName: string<br>- assemblyName: string<br>- Dictionary&lt;string, IConfigurable&gt;<br>- disposed: bool</td></tr><tr><td align="center">+ Start(): void<br>+ Stop(): void<br>+ GetConfigurables(): IReadOnlyDictionary&lt;string, IConfigurable&gt;<br>+ GetServiceState(): ServiceState<br>+ getModuleName(): string<br>+ GetassemblyName(): string<br>+ SetServiceName(name: string): void // Implemented as property<br>+ GetServiceName(): string // Implemented as property<br>+ GetServiceConsoleStream(): Stream // Implemented as property<br>- OnServiceStateChange(curr: ServiceState,&nbsp;prev: ServiceState): void<br># Dispose(disposing: bool): void<br>+ Dispose(): void<br></td></tr></tbody></table>" style="text;html=1;fillColor=none;overflow=fill;strokeColor=#f0f0f0;" parent="1" vertex="1"> | ||||||
|  |                     <mxGeometry x="950" y="950" width="400" height="410" as="geometry"/> | ||||||
|  |                 </mxCell> | ||||||
|  |                 <mxCell id="6" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;fontStyle=1" parent="1" source="v9q6W0nyI9kZyF3peKlB-2" target="v9q6W0nyI9kZyF3peKlB-1" edge="1"> | ||||||
|  |                     <mxGeometry relative="1" as="geometry"/> | ||||||
|  |                 </mxCell> | ||||||
|  |                 <mxCell id="v9q6W0nyI9kZyF3peKlB-2" value="<table border="1" width="100%" cellpadding="4" style="width: 100% ; height: 100% ; border-collapse: collapse"><tbody><tr><th align="center">ServiceManager (Use-case)</th></tr><tr><td align="center">- services: Dictionary&lt;string, Service&gt;<br>- modules: Dictionary&lt;string, Dictionary&lt;string, IGameServiceModule&gt;&gt;</td></tr><tr><td align="center">+ AddModule(assemblyName: string, module: IGameServiceModule): void<br>+ RemoveModule(assemblyName: string, moduleName string): void<br>+ CreateService(serviceName: string, assemblyName: string, moduleName: string): void<br>+ GetServiceNames(): IReadOnlyCollection&lt;string&gt;<br>+ GetServiceOptions(serviceName: string): IEnumerable&lt;string&gt;<br>+ SetServiceOptionValue(serviceName: string, optionName: string, string: value): bool<br>+ StartService(serviceName: string): void<br>+ StopService(serviceName: string): void<br>+ ExecuteCommand(serviceName: string, command: string): void<br>+ GetServiceConsoleStream(): Stream</td></tr></tbody></table>" style="text;html=1;fillColor=none;overflow=fill;strokeColor=#f0f0f0;" parent="1" vertex="1"> | ||||||
|  |                     <mxGeometry x="640" y="610" width="490" height="280" as="geometry"/> | ||||||
|  |                 </mxCell> | ||||||
|  |                 <mxCell id="v9q6W0nyI9kZyF3peKlB-4" value="IDisposable" style="shape=ext;double=1;rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1"> | ||||||
|  |                     <mxGeometry x="1410" y="820" width="120" height="80" as="geometry"/> | ||||||
|  |                 </mxCell> | ||||||
|  |                 <mxCell id="v9q6W0nyI9kZyF3peKlB-13" value="<table border="1" width="100%" cellpadding="4" style="width: 100% ; height: 100% ; border-collapse: collapse"><tbody><tr><th align="center">ModuleLoader (Gateway)</th></tr><tr><td align="center"></td></tr><tr><td align="center">- InstantiateServiceables(assembly: Assembly): void<br>- LoadAssembly(path: string): void<br>+ LoadModules(path: string): IEnumerable&lt;IGameServiceModule&gt;<br>+ LoadAllModules(path: string[]): IEnumerable&lt;IGameServiceModule&gt;<br>+ LoadAllModules(path: IEnumerable&lt;string&gt;): IEnumerable&lt;IGameServiceModule&gt;<br></td></tr></tbody></table>" style="text;html=1;fillColor=none;overflow=fill;strokeColor=#f0f0f0;" parent="1" vertex="1"> | ||||||
|  |                     <mxGeometry x="110" y="280" width="490" height="250" as="geometry"/> | ||||||
|  |                 </mxCell> | ||||||
|  |                 <mxCell id="titdvn9p0HDrujjw1N2D-4" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.75;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;dashed=1;endArrow=block;endFill=0;" parent="1" source="v9q6W0nyI9kZyF3peKlB-16" target="v9q6W0nyI9kZyF3peKlB-27" edge="1"> | ||||||
|  |                     <mxGeometry relative="1" as="geometry"/> | ||||||
|  |                 </mxCell> | ||||||
|  |                 <mxCell id="2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="v9q6W0nyI9kZyF3peKlB-16" target="v9q6W0nyI9kZyF3peKlB-2" edge="1"> | ||||||
|  |                     <mxGeometry relative="1" as="geometry"/> | ||||||
|  |                 </mxCell> | ||||||
|  |                 <mxCell id="3" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="v9q6W0nyI9kZyF3peKlB-16" target="v9q6W0nyI9kZyF3peKlB-13" edge="1"> | ||||||
|  |                     <mxGeometry relative="1" as="geometry"/> | ||||||
|  |                 </mxCell> | ||||||
|  |                 <mxCell id="4" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.75;exitDx=0;exitDy=0;entryX=1;entryY=0.25;entryDx=0;entryDy=0;" parent="1" source="v9q6W0nyI9kZyF3peKlB-16" target="v9q6W0nyI9kZyF3peKlB-23" edge="1"> | ||||||
|  |                     <mxGeometry relative="1" as="geometry"/> | ||||||
|  |                 </mxCell> | ||||||
|  |                 <mxCell id="v9q6W0nyI9kZyF3peKlB-16" value="<table border="1" width="100%" cellpadding="4" style="width: 100% ; height: 100% ; border-collapse: collapse"><tbody><tr><th align="center">ServiceController (Controller)</th></tr><tr><td align="center">- moduleLoader: ModuleLoader<br>- serviceManager: serviceManager</td></tr><tr><td align="center">+ LoadModulesInDirectory(directory: string): void<br>+ SaveServices(serviceDataDir: string): void<br>+ LoadServices(serviceDataDir: string): void</td></tr></tbody></table>" style="text;html=1;fillColor=none;overflow=fill;strokeColor=#f0f0f0;" parent="1" vertex="1"> | ||||||
|  |                     <mxGeometry x="640" y="280" width="432.5" height="250" as="geometry"/> | ||||||
|  |                 </mxCell> | ||||||
|  |                 <mxCell id="v9q6W0nyI9kZyF3peKlB-23" value="<table border="1" width="100%" cellpadding="4" style="width: 100% ; height: 100% ; border-collapse: collapse"><tbody><tr><th align="center">ServiceGateway (Gateway)</th></tr><tr><td align="center">- dataDirectory: string</td></tr><tr><td align="center">+ SaveService(name: string, assemblyName: string, moduleName: string): void<br>+ GetServiceName(path: string): string<br>+ GetServiceModuleName(path: string): string<br>+ GetAllServiceInfoPaths() IEnumerable&lt;string&gt;</td></tr></tbody></table>" style="text;html=1;fillColor=none;overflow=fill;strokeColor=#f0f0f0;" parent="1" vertex="1"> | ||||||
|  |                     <mxGeometry x="110" y="545" width="490" height="230" as="geometry"/> | ||||||
|  |                 </mxCell> | ||||||
|  |                 <mxCell id="v9q6W0nyI9kZyF3peKlB-27" value="<table border="1" width="100%" cellpadding="4" style="width: 100% ; height: 100% ; border-collapse: collapse"><tbody><tr><th align="center">ICommandable &lt;I&gt;</th></tr><tr><td align="center"></td></tr><tr><td align="center">+ GetPrefix(): string<br>+ Validate(string input): bool<br>+ Execute(string input): void<br>+ Help(): string</td></tr></tbody></table>" style="text;html=1;fillColor=none;overflow=fill;strokeColor=#f0f0f0;" parent="1" vertex="1"> | ||||||
|  |                     <mxGeometry x="900" y="50" width="250" height="170" as="geometry"/> | ||||||
|  |                 </mxCell> | ||||||
|  |                 <mxCell id="titdvn9p0HDrujjw1N2D-5" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0;exitDx=0;exitDy=0;entryX=0.75;entryY=1;entryDx=0;entryDy=0;dashed=1;endArrow=block;endFill=0;" parent="1" source="titdvn9p0HDrujjw1N2D-1" target="v9q6W0nyI9kZyF3peKlB-27" edge="1"> | ||||||
|  |                     <mxGeometry relative="1" as="geometry"/> | ||||||
|  |                 </mxCell> | ||||||
|  |                 <mxCell id="HWr5KmjBEDiam6OlhXxB-2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.25;exitY=0;exitDx=0;exitDy=0;entryX=1;entryY=1;entryDx=0;entryDy=0;endArrow=open;endFill=0;" parent="1" source="titdvn9p0HDrujjw1N2D-1" target="v9q6W0nyI9kZyF3peKlB-27" edge="1"> | ||||||
|  |                     <mxGeometry relative="1" as="geometry"/> | ||||||
|  |                 </mxCell> | ||||||
|  |                 <mxCell id="titdvn9p0HDrujjw1N2D-1" value="<table border="1" width="100%" cellpadding="4" style="width: 100% ; height: 100% ; border-collapse: collapse"><tbody><tr><th align="center">CommandFork (Controller)</th></tr><tr><td align="center">- commands: Dictionary&lt;string, ICommandable&gt;</td></tr><tr><td align="center"><br></td></tr></tbody></table>" style="text;html=1;fillColor=none;overflow=fill;strokeColor=#f0f0f0;" parent="1" vertex="1"> | ||||||
|  |                     <mxGeometry x="1120" y="280" width="280" height="140" as="geometry"/> | ||||||
|  |                 </mxCell> | ||||||
|  |             </root> | ||||||
|  |         </mxGraphModel> | ||||||
|  |     </diagram> | ||||||
|  | </mxfile> | ||||||
| @@ -0,0 +1,7 @@ | |||||||
|  | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |  | ||||||
|  |   <PropertyGroup> | ||||||
|  |     <TargetFramework>netstandard2.0</TargetFramework> | ||||||
|  |   </PropertyGroup> | ||||||
|  |  | ||||||
|  | </Project> | ||||||
							
								
								
									
										9
									
								
								src/GameServiceWarden.ModuleAPI/IConfigurable.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/GameServiceWarden.ModuleAPI/IConfigurable.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | namespace GameServiceWarden.ModuleAPI | ||||||
|  | { | ||||||
|  |     public interface IConfigurable | ||||||
|  |     { | ||||||
|  |         string OptionName { get; } | ||||||
|  |         bool SetValue(string value); | ||||||
|  |         string GetValue(); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										15
									
								
								src/GameServiceWarden.ModuleAPI/IGameService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/GameServiceWarden.ModuleAPI/IGameService.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.IO; | ||||||
|  |  | ||||||
|  | namespace GameServiceWarden.ModuleAPI | ||||||
|  | { | ||||||
|  |     public interface IGameService | ||||||
|  |     { | ||||||
|  |         event EventHandler<ServiceState> StateChangeEvent; | ||||||
|  |         IReadOnlyCollection<IConfigurable> Configurables{ get; } | ||||||
|  |         void InitializeService(TextWriter stream); | ||||||
|  |         void ElegantShutdown(); | ||||||
|  |         void ExecuteCommand(string command); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										28
									
								
								src/GameServiceWarden.ModuleAPI/IGameServiceModule.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/GameServiceWarden.ModuleAPI/IGameServiceModule.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | using System.Collections.Generic; | ||||||
|  |  | ||||||
|  | namespace GameServiceWarden.ModuleAPI | ||||||
|  | { | ||||||
|  |     public interface IGameServiceModule | ||||||
|  |     { | ||||||
|  |         /// <summary> | ||||||
|  |         /// The name of the game service this module handles. | ||||||
|  |         /// </summary> | ||||||
|  |         string Name { get; } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Description of the game service this module handles. | ||||||
|  |         /// </summary> | ||||||
|  |         string Description { get; } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// The authors responsible for creating this module. | ||||||
|  |         /// </summary> | ||||||
|  |         IEnumerable<string> Authors { get; } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Creates an instance of a the service to be used. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <returns>The <see cref="IGameService"/> responsible for the instance of the game service.</returns> | ||||||
|  |         IGameService CreateGameService(); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								src/GameServiceWarden.ModuleAPI/ServiceState.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/GameServiceWarden.ModuleAPI/ServiceState.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | using System; | ||||||
|  |  | ||||||
|  | namespace GameServiceWarden.ModuleAPI | ||||||
|  | { | ||||||
|  |     public enum ServiceState | ||||||
|  |     { | ||||||
|  |         Stopped, | ||||||
|  |         Running,  | ||||||
|  |         Error, | ||||||
|  |         PendingRestart | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,21 @@ | |||||||
|  | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |  | ||||||
|  |   <PropertyGroup> | ||||||
|  |     <TargetFramework>netcoreapp3.1</TargetFramework> | ||||||
|  |  | ||||||
|  |     <IsPackable>false</IsPackable> | ||||||
|  |   </PropertyGroup> | ||||||
|  |  | ||||||
|  |   <ItemGroup> | ||||||
|  |     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" /> | ||||||
|  |     <PackageReference Include="xunit" Version="2.4.1" /> | ||||||
|  |     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> | ||||||
|  |     <PackageReference Include="coverlet.collector" Version="1.2.0" /> | ||||||
|  |   </ItemGroup> | ||||||
|  |  | ||||||
|  |   <ItemGroup> | ||||||
|  |     <ProjectReference Include="..\..\src\GameServiceWarden.Host\GameServiceWarden.Host.csproj" /> | ||||||
|  |     <ProjectReference Include="..\..\src\GameServiceWarden.ModuleAPI\GameServiceWarden.ModuleAPI.csproj" /> | ||||||
|  |   </ItemGroup> | ||||||
|  |  | ||||||
|  | </Project> | ||||||
| @@ -0,0 +1,21 @@ | |||||||
|  | using GameServiceWarden.ModuleAPI; | ||||||
|  |  | ||||||
|  | namespace GameServiceWarden.Host.Tests.Modules | ||||||
|  | { | ||||||
|  |     public class FakeConfigurable : IConfigurable | ||||||
|  |     { | ||||||
|  |         private string value; | ||||||
|  |         public string OptionName => "FakeOption"; | ||||||
|  |  | ||||||
|  |         public string GetValue() | ||||||
|  |         { | ||||||
|  |             return value; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public bool SetValue(string value) | ||||||
|  |         { | ||||||
|  |             this.value = value; | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										32
									
								
								tests/GameServiceWarden.Host.Tests/Modules/FakeService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								tests/GameServiceWarden.Host.Tests/Modules/FakeService.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.IO; | ||||||
|  | using GameServiceWarden.ModuleAPI; | ||||||
|  |  | ||||||
|  | namespace GameServiceWarden.Host.Tests.Modules | ||||||
|  | { | ||||||
|  |     public class FakeService : IGameService | ||||||
|  |     { | ||||||
|  |         public IReadOnlyCollection<IConfigurable> Configurables { get; set; } = new HashSet<IConfigurable>(); | ||||||
|  |  | ||||||
|  |         public event EventHandler<ServiceState> StateChangeEvent; | ||||||
|  |  | ||||||
|  |         public ServiceState CurrentState { get; private set; } = ServiceState.Stopped; | ||||||
|  |  | ||||||
|  |         public void ElegantShutdown() | ||||||
|  |         { | ||||||
|  |             CurrentState = ServiceState.Stopped; | ||||||
|  |             StateChangeEvent?.Invoke(this, CurrentState); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public void ExecuteCommand(string command) | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public void InitializeService(TextWriter stream) | ||||||
|  |         { | ||||||
|  |             CurrentState = ServiceState.Running; | ||||||
|  |             StateChangeEvent?.Invoke(this, CurrentState); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										141
									
								
								tests/GameServiceWarden.Host.Tests/Modules/ServiceInfoTest.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								tests/GameServiceWarden.Host.Tests/Modules/ServiceInfoTest.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,141 @@ | |||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.IO; | ||||||
|  | using GameServiceWarden.Host.Modules; | ||||||
|  | using GameServiceWarden.ModuleAPI; | ||||||
|  | using Xunit; | ||||||
|  |  | ||||||
|  | namespace GameServiceWarden.Host.Tests.Modules | ||||||
|  | { | ||||||
|  |     // Testing convention from: https://docs.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices | ||||||
|  |     // Fakes are generic test objects,  | ||||||
|  |     // mocks are the objects being asserted upon,  | ||||||
|  |     // stubs are objects used as part of the test. | ||||||
|  |     public class ServiceInfoTest | ||||||
|  |     { | ||||||
|  |         //MethodTested_ScenarioTested_ExpectedBehavior | ||||||
|  |         [Fact] | ||||||
|  |         public void Start_FromStopped_StateIsRunning() | ||||||
|  |         { | ||||||
|  |             //Arrange, Act, Assert | ||||||
|  |             IGameService stubGameService = new FakeService(); | ||||||
|  |             ServiceInfo serviceInfo = new ServiceInfo(stubGameService, "FakeModule", "FakeAssembly"); | ||||||
|  |             serviceInfo.Start(); | ||||||
|  |             Assert.Equal(ServiceState.Running, serviceInfo.GetServiceState()); | ||||||
|  |             serviceInfo.Dispose(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void Stop_FromStart_Stopped() | ||||||
|  |         { | ||||||
|  |             IGameService stubService = new FakeService(); | ||||||
|  |             ServiceInfo serviceInfo = new ServiceInfo(stubService, "FakeModule", "FakeAssembly"); | ||||||
|  |             serviceInfo.Start(); | ||||||
|  |             serviceInfo.Stop(); | ||||||
|  |             Assert.Equal(ServiceState.Stopped, serviceInfo.GetServiceState()); | ||||||
|  |             serviceInfo.Dispose(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void GetConfigurables_ServiceStopped_ReturnsConfigurables() | ||||||
|  |         { | ||||||
|  |             //Given | ||||||
|  |             FakeService stubService = new FakeService(); | ||||||
|  |             FakeConfigurable stubConfigurable = new FakeConfigurable(); | ||||||
|  |             HashSet<IConfigurable> configurables = new HashSet<IConfigurable>(); | ||||||
|  |             configurables.Add(stubConfigurable); | ||||||
|  |             stubService.Configurables = configurables; | ||||||
|  |             ServiceInfo serviceInfo = new ServiceInfo(stubService, "FakeModule", "FakeAssembly"); | ||||||
|  |             //When | ||||||
|  |             serviceInfo.Start(); | ||||||
|  |             //Then | ||||||
|  |             Assert.Contains(stubConfigurable, serviceInfo.GetConfigurables().Values); | ||||||
|  |             serviceInfo.Dispose(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void GetServiceState_ServiceNotStarted_ReturnsStoppedState() | ||||||
|  |         { | ||||||
|  |             //Given | ||||||
|  |             IGameService stubService = new FakeService(); | ||||||
|  |             ServiceInfo serviceInfo = new ServiceInfo(stubService, "FakeModule", "FakeAssembly"); | ||||||
|  |             //Then | ||||||
|  |             Assert.Equal(ServiceState.Stopped, serviceInfo.GetServiceState()); | ||||||
|  |             serviceInfo.Dispose(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void GetServiceState_ServiceStarted_ReturnsRunningState() | ||||||
|  |         { | ||||||
|  |             //Given | ||||||
|  |             IGameService stubService = new FakeService(); | ||||||
|  |             ServiceInfo serviceInfo = new ServiceInfo(stubService, "FakeModule", "FakeAssembly"); | ||||||
|  |             //When | ||||||
|  |             serviceInfo.Start(); | ||||||
|  |             //Then | ||||||
|  |             Assert.Equal(ServiceState.Running, serviceInfo.GetServiceState()); | ||||||
|  |             serviceInfo.Dispose(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void GetModuleName_ServiceNotStarted_ReturnsSetName() | ||||||
|  |         { | ||||||
|  |             //Given | ||||||
|  |             const string MODULE_NAME = "FakeModule"; | ||||||
|  |             IGameService stubService = new FakeService(); | ||||||
|  |             ServiceInfo serviceInfo = new ServiceInfo(stubService, MODULE_NAME, "FakeAssembly"); | ||||||
|  |             //Then | ||||||
|  |             Assert.Equal(MODULE_NAME, serviceInfo.GetModuleName()); | ||||||
|  |             serviceInfo.Dispose(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void GetAssemblyName_ServiceNotStarted_ReturnsSetAssemblyName() | ||||||
|  |         { | ||||||
|  |             //Given | ||||||
|  |             const string ASSEMBLY_NAME = "FakeAssembly"; | ||||||
|  |             IGameService stubService = new FakeService(); | ||||||
|  |             ServiceInfo serviceInfo = new ServiceInfo(stubService, "FakeModule", ASSEMBLY_NAME); | ||||||
|  |             //Then | ||||||
|  |             Assert.Equal(ASSEMBLY_NAME, serviceInfo.GetAssemblyName()); | ||||||
|  |             serviceInfo.Dispose(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void SetAndGetServiceName_ServiceNotStartedSingleThread_ServiceNameUpdated() | ||||||
|  |         { | ||||||
|  |             //Given | ||||||
|  |             const string SERVICE_NAME = "Service"; | ||||||
|  |             IGameService stubService = new FakeService(); | ||||||
|  |             ServiceInfo serviceInfo = new ServiceInfo(stubService, "FakeModule", "FakeAssemblyName"); | ||||||
|  |             //When | ||||||
|  |             serviceInfo.ServiceName = SERVICE_NAME; | ||||||
|  |             //Then | ||||||
|  |             Assert.Equal(SERVICE_NAME, serviceInfo.ServiceName); | ||||||
|  |             serviceInfo.Dispose(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void ServiceConsoleStream_ServiceNotStarted_NullReturned() | ||||||
|  |         { | ||||||
|  |             //Given | ||||||
|  |             IGameService stubService = new FakeService(); | ||||||
|  |             ServiceInfo serviceInfo = new ServiceInfo(stubService, "FakeModule", "FakeAssembly"); | ||||||
|  |             //Then | ||||||
|  |             Assert.Null(serviceInfo.ServiceConsoleStream); | ||||||
|  |             serviceInfo.Dispose(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void ServiceConsoleStream_ServiceStarted_StreamReturned() | ||||||
|  |         { | ||||||
|  |             //Given | ||||||
|  |             IGameService stubService = new FakeService(); | ||||||
|  |             ServiceInfo serviceInfo = new ServiceInfo(stubService, "FakeModule", "FakeAssembly"); | ||||||
|  |             //When | ||||||
|  |             serviceInfo.Start(); | ||||||
|  |             //Then | ||||||
|  |             Assert.IsAssignableFrom<Stream>(serviceInfo.ServiceConsoleStream); | ||||||
|  |             serviceInfo.Dispose(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user