Compare commits
65 Commits
Author | SHA1 | Date | |
---|---|---|---|
4dc4819bd8 | |||
a7da520faf | |||
025999ab59 | |||
3b69b8666c | |||
ad32b00dd0 | |||
708afbff6b | |||
841bdda15e | |||
b23ece8c8c | |||
3506020d9b | |||
4cf1ff53a9 | |||
a1401c63e9 | |||
0073efc9ac | |||
f9cbd0871d | |||
4df970a542 | |||
2b5a43f95f | |||
1f9a2a4a5f | |||
7438a76bf7 | |||
f61bbd3a9e | |||
f5a181d2f2 | |||
35a2765559 | |||
cac5ca054c | |||
58c5b46dff | |||
0991dc6214 | |||
a0775fdf8e | |||
911724627e | |||
a6f4d918cb | |||
78e9e185c2 | |||
526e657f59 | |||
1ae4de4f73 | |||
a44ad71f59 | |||
56e12ec22a | |||
a9ff809ba8 | |||
cfecc7c7b3 | |||
eda8da3383 | |||
7abc1be4bf | |||
7e6e6a9333 | |||
8ebe9ee9f7 | |||
ccff87fc63 | |||
89b23258aa | |||
455efcbfe3 | |||
220839919b | |||
3f1836d468 | |||
d4f9cb10bf | |||
dfc54fdc00 | |||
56259ac419 | |||
44512329fd | |||
97967ce0c6 | |||
067c8f5824 | |||
f09a95799f | |||
ba970dac4c | |||
522b34810e | |||
832ab632c5 | |||
c9e6706e9b | |||
119f917f0b | |||
f402b8b7f2 | |||
c7fadd8166 | |||
0cf2335aa7 | |||
334fd37dc6 | |||
53e8427b66 | |||
4996982c89 | |||
3a49502970 | |||
acd735d9df | |||
e4f2ebf792 | |||
ca7221ea42 | |||
6467c178c3 |
11
.devcontainer/Dockerfile
Normal file
11
.devcontainer/Dockerfile
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
FROM mcr.microsoft.com/devcontainers/anaconda:1-3
|
||||||
|
|
||||||
|
# Copy environment.yml (if found) to a temp location so we update the environment. Also
|
||||||
|
# copy "noop.txt" so the COPY instruction does not fail if no environment.yml exists.
|
||||||
|
COPY environment.yml* .devcontainer/noop.txt /tmp/conda-tmp/
|
||||||
|
RUN if [ -f "/tmp/conda-tmp/environment.yml" ]; then umask 0002 && /opt/conda/bin/conda env update -n base -f /tmp/conda-tmp/environment.yml; fi \
|
||||||
|
&& rm -rf /tmp/conda-tmp
|
||||||
|
|
||||||
|
# [Optional] Uncomment this section to install additional OS packages.
|
||||||
|
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||||
|
# && apt-get -y install --no-install-recommends <your-package-list-here>
|
30
.devcontainer/devcontainer.json
Normal file
30
.devcontainer/devcontainer.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||||
|
// README at: https://github.com/devcontainers/templates/tree/main/src/anaconda
|
||||||
|
{
|
||||||
|
"name": "Anaconda (Python 3)",
|
||||||
|
"build": {
|
||||||
|
"context": "..",
|
||||||
|
"dockerfile": "Dockerfile"
|
||||||
|
},
|
||||||
|
|
||||||
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
"extensions": [
|
||||||
|
"ms-dotnettools.vscode-dotnet-runtime",
|
||||||
|
"svelte.svelte-vscode",
|
||||||
|
"syler.sass-indented",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"ms-dotnettools.csharp",
|
||||||
|
"hediet.vscode-drawio"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"python.defaultInterpreterPath": "/opt/conda/bin/python",
|
||||||
|
"dotnet.dotnetPath": "/opt/conda/lib/dotnet/",
|
||||||
|
"omnisharp.dotNetCliPaths": [
|
||||||
|
"/opt/conda/lib/dotnet/dotnet"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
3
.devcontainer/noop.txt
Normal file
3
.devcontainer/noop.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
This file copied into the container along with environment.yml* from the parent
|
||||||
|
folder. This file is included to prevents the Dockerfile COPY instruction from
|
||||||
|
failing if no environment.yml is found.
|
0
.gitmodules
vendored
Normal file
0
.gitmodules
vendored
Normal file
27
.vscode/launch.json
vendored
Normal file
27
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to find out which attributes exist for C# debugging
|
||||||
|
// Use hover for the description of the existing attributes
|
||||||
|
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": ".NET Core Launch (console)",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "build",
|
||||||
|
// If you have changed target frameworks, make sure to update the program path.
|
||||||
|
"program": "${workspaceFolder}/src/GameServiceWarden.Core/bin/Debug/netcoreapp3.1/GameServiceWarden.Core.dll",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}/src/GameServiceWarden.Core",
|
||||||
|
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
|
||||||
|
"console": "internalConsole",
|
||||||
|
"stopAtEntry": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": ".NET Core Attach",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "attach",
|
||||||
|
"processId": "${command:pickProcess}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
42
.vscode/tasks.json
vendored
Normal file
42
.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "build",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"${workspaceFolder}/src/GameServiceWarden.Core/GameServiceWarden.Core.csproj",
|
||||||
|
"/property:GenerateFullPaths=true",
|
||||||
|
"/consoleloggerparameters:NoSummary"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "publish",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"publish",
|
||||||
|
"${workspaceFolder}/src/GameServiceWarden.Core/GameServiceWarden.Core.csproj",
|
||||||
|
"/property:GenerateFullPaths=true",
|
||||||
|
"/consoleloggerparameters:NoSummary"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "watch",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"watch",
|
||||||
|
"run",
|
||||||
|
"${workspaceFolder}/src/GameServiceWarden.Core/GameServiceWarden.Core.csproj",
|
||||||
|
"/property:GenerateFullPaths=true",
|
||||||
|
"/consoleloggerparameters:NoSummary"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.Core.Tests.Collection
|
||||||
|
{
|
||||||
|
public class FakeDisposable : IDisposable
|
||||||
|
{
|
||||||
|
private string value;
|
||||||
|
private bool disposedValue;
|
||||||
|
|
||||||
|
public FakeDisposable(string value)
|
||||||
|
{
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsDisposed() {
|
||||||
|
return disposedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposedValue)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
disposedValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
using GameServiceWarden.Core.Collection;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.Core.Tests.Collection
|
||||||
|
{
|
||||||
|
public class LRUCacheTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Use_SufficientSpace_StoredUsed()
|
||||||
|
{
|
||||||
|
//Given
|
||||||
|
string data = "data";
|
||||||
|
LRUCache<int, string> cache = new LRUCache<int, string>(10);
|
||||||
|
//When
|
||||||
|
cache.Use(0, () => data);
|
||||||
|
//Then
|
||||||
|
Assert.Same(data, cache.Use(0, () => "other"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Use_InsufficientSpace_LastUsedRemoved()
|
||||||
|
{
|
||||||
|
//Given
|
||||||
|
string[] data = new string[] { "a", "b", "c" };
|
||||||
|
LRUCache<int, string> cache = new LRUCache<int, string>(2);
|
||||||
|
//When
|
||||||
|
for (int i = 0; i < data.Length; i++)
|
||||||
|
{
|
||||||
|
cache.Use(i, () => data[i]);
|
||||||
|
}
|
||||||
|
//Then
|
||||||
|
Assert.Contains("c", cache);
|
||||||
|
Assert.Contains("b", cache);
|
||||||
|
Assert.DoesNotContain("a", cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsCached_CachedData_True()
|
||||||
|
{
|
||||||
|
//Given
|
||||||
|
string[] data = new string[] { "a", "b", "c" };
|
||||||
|
LRUCache<int, string> cache = new LRUCache<int, string>(2);
|
||||||
|
//When
|
||||||
|
for (int i = 0; i < data.Length; i++)
|
||||||
|
{
|
||||||
|
cache.Use(i, () => data[i]);
|
||||||
|
}
|
||||||
|
//Then
|
||||||
|
Assert.True(cache.IsCached(2));
|
||||||
|
Assert.True(cache.IsCached(1));
|
||||||
|
Assert.False(cache.IsCached(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Use_CleanupDelSet_DataChanged()
|
||||||
|
{
|
||||||
|
//Given
|
||||||
|
FakeDisposable[] data = new FakeDisposable[] { new FakeDisposable("a"), new FakeDisposable("b"), new FakeDisposable("c")};
|
||||||
|
LRUCache<int, FakeDisposable> cache = new LRUCache<int, FakeDisposable>(2, (d) => d.Dispose());
|
||||||
|
//When
|
||||||
|
for (int i = 0; i < data.Length; i++)
|
||||||
|
{
|
||||||
|
cache.Use(i, () => data[i]);
|
||||||
|
}
|
||||||
|
//Then
|
||||||
|
Assert.True(data[0].IsDisposed());
|
||||||
|
Assert.False(data[1].IsDisposed());
|
||||||
|
Assert.False(data[2].IsDisposed());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
|
||||||
|
<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" />
|
||||||
|
<PackageReference Include="XunitXml.TestLogger" Version="3.0.70" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\GameServiceWarden\GameServiceWarden.Core\GameServiceWarden.Core.csproj" />
|
||||||
|
<ProjectReference Include="..\..\GameServiceWarden\GameServiceWarden.ModuleFramework\GameServiceWarden.ModuleFramework.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
@ -0,0 +1,84 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.IO;
|
||||||
|
using GameServiceWarden.Core.Module;
|
||||||
|
using GameServiceWarden.Core.Persistence;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.Core.Tests.Modules
|
||||||
|
{
|
||||||
|
public class FakePersistence<V> : IPersistent<V>
|
||||||
|
{
|
||||||
|
private IDictionary<string, V> backing = new Dictionary<string, V>();
|
||||||
|
public V this[string key] { get { return backing[key]; } set { backing[key] = value; } }
|
||||||
|
|
||||||
|
public string MapDirectory { get { return "fake/directory/"; } }
|
||||||
|
|
||||||
|
public ICollection<string> Keys { get { return backing.Keys; } }
|
||||||
|
|
||||||
|
public ICollection<V> Values { get { return backing.Values; } }
|
||||||
|
|
||||||
|
public int Count { get { return backing.Count; } }
|
||||||
|
|
||||||
|
public bool IsReadOnly { get { return false; } }
|
||||||
|
|
||||||
|
IEnumerable<V> IReadOnlyPersistent<V>.Values => backing.Values;
|
||||||
|
|
||||||
|
IEnumerable<string> IReadOnlyPersistent<V>.Keys => backing.Keys;
|
||||||
|
|
||||||
|
public void AddToPersistence(string key, V value)
|
||||||
|
{
|
||||||
|
backing.Add(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(KeyValuePair<string, V> item)
|
||||||
|
{
|
||||||
|
backing.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(string key, ServiceDescriptor value)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
backing.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContainsKey(string key)
|
||||||
|
{
|
||||||
|
return backing.ContainsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<KeyValuePair<string, V>> GetEnumerator()
|
||||||
|
{
|
||||||
|
return backing.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetPathForKey(string key)
|
||||||
|
{
|
||||||
|
return MapDirectory + Path.DirectorySeparatorChar + key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Delete(string key)
|
||||||
|
{
|
||||||
|
return backing.Remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(KeyValuePair<string, V> item)
|
||||||
|
{
|
||||||
|
return backing.Remove(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryLoadValue(string key, [MaybeNullWhen(false)] out V value)
|
||||||
|
{
|
||||||
|
return backing.TryGetValue(key, out value);
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return backing.GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using GameServiceWarden.ModuleFramework;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.Core.Tests.Modules
|
||||||
|
{
|
||||||
|
public class FakeService : IService
|
||||||
|
{
|
||||||
|
public IReadOnlyCollection<IServiceConfigurable> Configurables { get; set; }
|
||||||
|
|
||||||
|
public event EventHandler<ServiceState> StateChangeEvent;
|
||||||
|
public event EventHandler<string> UpdateLogEvent;
|
||||||
|
|
||||||
|
public ServiceState CurrentState { get; private set; } = ServiceState.Stopped;
|
||||||
|
private MemoryStream memoryStream;
|
||||||
|
private StreamWriter consoleWriter;
|
||||||
|
private Stack<Task> taskStack = new Stack<Task>();
|
||||||
|
|
||||||
|
public FakeService(params IServiceConfigurable[] configurables)
|
||||||
|
{
|
||||||
|
HashSet<IServiceConfigurable> modifiable = new HashSet<IServiceConfigurable>();
|
||||||
|
foreach (IServiceConfigurable configurable in configurables)
|
||||||
|
{
|
||||||
|
modifiable.Add(configurable);
|
||||||
|
}
|
||||||
|
this.Configurables = modifiable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ElegantShutdown()
|
||||||
|
{
|
||||||
|
CurrentState = ServiceState.Stopped;
|
||||||
|
StateChangeEvent?.Invoke(this, ServiceState.Stopped);
|
||||||
|
Task task;
|
||||||
|
while(taskStack.TryPop(out task)) {
|
||||||
|
if (task.IsCompleted) {
|
||||||
|
task.Wait();
|
||||||
|
} else {
|
||||||
|
throw new InvalidOperationException("A task was not completed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ExecuteCommand(string command)
|
||||||
|
{
|
||||||
|
taskStack.Push(consoleWriter.WriteLineAsync(command));
|
||||||
|
taskStack.Push(consoleWriter.FlushAsync());
|
||||||
|
UpdateLogEvent?.Invoke(this, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InitializeService()
|
||||||
|
{
|
||||||
|
CurrentState = ServiceState.Running;
|
||||||
|
memoryStream = new MemoryStream();
|
||||||
|
this.consoleWriter = new StreamWriter(memoryStream);
|
||||||
|
StateChangeEvent?.Invoke(this, ServiceState.Running);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] GetLogBuffer()
|
||||||
|
{
|
||||||
|
return memoryStream.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
using GameServiceWarden.ModuleFramework;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.Core.Tests.Modules
|
||||||
|
{
|
||||||
|
public class FakeServiceConfigurable : IServiceConfigurable
|
||||||
|
{
|
||||||
|
private string value;
|
||||||
|
public string OptionName { get; private set; }
|
||||||
|
|
||||||
|
public FakeServiceConfigurable(string optionName)
|
||||||
|
{
|
||||||
|
this.OptionName = optionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetValue()
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetValue(string value)
|
||||||
|
{
|
||||||
|
this.value = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using GameServiceWarden.InteractionAPI.Module;
|
||||||
|
using GameServiceWarden.Core.Module;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.Core.Tests.Modules
|
||||||
|
{
|
||||||
|
public class FakeServiceManagerMonitor : IServiceManagerMonitor
|
||||||
|
{
|
||||||
|
public List<ServiceManagerTotal> states = new List<ServiceManagerTotal>();
|
||||||
|
public List<ServiceManagerDelta> deltas = new List<ServiceManagerDelta>();
|
||||||
|
public ServiceManagerTotal this[int i]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return states[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Present(ServiceManagerTotal state)
|
||||||
|
{
|
||||||
|
states.Add(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Present(ServiceManagerDelta delta)
|
||||||
|
{
|
||||||
|
deltas.Add(delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceManagerTotal GetLastState()
|
||||||
|
{
|
||||||
|
return states[states.Count - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceManagerDelta GetLastDelta() {
|
||||||
|
return deltas[deltas.Count - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using GameServiceWarden.ModuleFramework;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.Core.Tests.Modules
|
||||||
|
{
|
||||||
|
public class FakeServiceModule : IServiceModule
|
||||||
|
{
|
||||||
|
public string Name => "FakeModule";
|
||||||
|
|
||||||
|
public string Description => "A fake module for testing.";
|
||||||
|
|
||||||
|
private IServiceConfigurable[] configurables;
|
||||||
|
public FakeServiceModule(params IServiceConfigurable[] configurables)
|
||||||
|
{
|
||||||
|
this.configurables = configurables;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> Authors { get; private set; } = new string[] { "FakeAuthor", "FakeAuthor2" };
|
||||||
|
|
||||||
|
public IService InstantiateService(string workspace)
|
||||||
|
{
|
||||||
|
return new FakeService(configurables);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,165 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using GameServiceWarden.Core.Module;
|
||||||
|
using GameServiceWarden.ModuleFramework;
|
||||||
|
using Xunit;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.Core.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.
|
||||||
|
[CollectionDefinition("Service", DisableParallelization = true)]
|
||||||
|
public class ServiceDescriptorTest
|
||||||
|
{
|
||||||
|
private readonly ITestOutputHelper output;
|
||||||
|
private readonly XUnitLogger logger;
|
||||||
|
|
||||||
|
public ServiceDescriptorTest(ITestOutputHelper output)
|
||||||
|
{
|
||||||
|
this.output = output;
|
||||||
|
logger = new XUnitLogger(output);
|
||||||
|
Logger.AddLogListener(logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
//MethodTested_ScenarioTested_ExpectedBehavior
|
||||||
|
[Fact]
|
||||||
|
public void Start_FromStopped_StateIsRunning()
|
||||||
|
{
|
||||||
|
//Arrange, Act, Assert
|
||||||
|
const string SERVICE_NAME = "Start_FromStopped_StateIsRunning";
|
||||||
|
IService stubService = new FakeService();
|
||||||
|
ServiceDescriptor serviceInfo = new ServiceDescriptor(stubService, SERVICE_NAME, "FakeModule", "FakeAssembly");
|
||||||
|
serviceInfo.Start();
|
||||||
|
Assert.Equal(ServiceState.Running, serviceInfo.GetServiceState());
|
||||||
|
serviceInfo.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Stop_FromStart_Stopped()
|
||||||
|
{
|
||||||
|
const string SERVICE_NAME = "Stop_FromStart_Stopped";
|
||||||
|
IService stubService = new FakeService();
|
||||||
|
ServiceDescriptor serviceInfo = new ServiceDescriptor(stubService, SERVICE_NAME, "FakeModule", "FakeAssembly");
|
||||||
|
serviceInfo.Start();
|
||||||
|
serviceInfo.Stop();
|
||||||
|
Assert.Equal(ServiceState.Stopped, serviceInfo.GetServiceState());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetConfigurableOptions_ServiceStopped_ReturnsConfigurables()
|
||||||
|
{
|
||||||
|
//Given
|
||||||
|
const string SERVICE_NAME = "GetConfigurableOptions_ServiceStopped_ReturnsConfigurables";
|
||||||
|
FakeService stubService = new FakeService();
|
||||||
|
FakeServiceConfigurable stubConfigurable = new FakeServiceConfigurable("Option");
|
||||||
|
HashSet<IServiceConfigurable> configurables = new HashSet<IServiceConfigurable>();
|
||||||
|
configurables.Add(stubConfigurable);
|
||||||
|
stubService.Configurables = configurables;
|
||||||
|
ServiceDescriptor serviceInfo = new ServiceDescriptor(stubService, SERVICE_NAME, "FakeModule", "FakeAssembly");
|
||||||
|
//Then
|
||||||
|
Assert.Contains<string>(stubConfigurable.OptionName, serviceInfo.GetConfigurableOptions());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SetAndGetConfigurationValue_ServiceStopped_AppropriateValueReturned()
|
||||||
|
{
|
||||||
|
//Given
|
||||||
|
const string SERVICE_NAME = "SetAndGetConfigurationValue_ServiceStopped_AppropriateValueReturned";
|
||||||
|
FakeService stubService = new FakeService();
|
||||||
|
FakeServiceConfigurable stubConfigurable = new FakeServiceConfigurable("Option");
|
||||||
|
HashSet<IServiceConfigurable> configurables = new HashSet<IServiceConfigurable>();
|
||||||
|
configurables.Add(stubConfigurable);
|
||||||
|
stubService.Configurables = configurables;
|
||||||
|
ServiceDescriptor serviceInfo = new ServiceDescriptor(stubService, SERVICE_NAME, "FakeModule", "FakeAssembly");
|
||||||
|
//When
|
||||||
|
serviceInfo.SetConfigurableValue(stubConfigurable.OptionName, "success");
|
||||||
|
//Then
|
||||||
|
Assert.True("success".Equals(serviceInfo.GetConfigurableValue(stubConfigurable.OptionName)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetServiceState_ServiceNotStarted_ReturnsStoppedState()
|
||||||
|
{
|
||||||
|
//Given
|
||||||
|
const string SERVICE_NAME = "GetServiceState_ServiceNotStarted_ReturnsStoppedState";
|
||||||
|
IService stubService = new FakeService();
|
||||||
|
ServiceDescriptor serviceInfo = new ServiceDescriptor(stubService, SERVICE_NAME, "FakeModule", "FakeAssembly");
|
||||||
|
//Then
|
||||||
|
Assert.Equal(ServiceState.Stopped, serviceInfo.GetServiceState());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetServiceState_ServiceStarted_ReturnsRunningState()
|
||||||
|
{
|
||||||
|
//Given
|
||||||
|
const string SERVICE_NAME = "GetServiceState_ServiceStarted_ReturnsRunningState";
|
||||||
|
IService stubService = new FakeService();
|
||||||
|
ServiceDescriptor serviceInfo = new ServiceDescriptor(stubService, SERVICE_NAME, "FakeModule", "FakeAssembly");
|
||||||
|
//When
|
||||||
|
serviceInfo.Start();
|
||||||
|
//Then
|
||||||
|
Assert.Equal(ServiceState.Running, serviceInfo.GetServiceState());
|
||||||
|
serviceInfo.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetModuleName_ServiceNotStarted_ReturnsSetName()
|
||||||
|
{
|
||||||
|
//Given
|
||||||
|
const string SERVICE_NAME = "GetModuleName_ServiceNotStarted_ReturnsSetName";
|
||||||
|
const string MODULE_NAME = "FakeModule";
|
||||||
|
IService stubService = new FakeService();
|
||||||
|
ServiceDescriptor serviceInfo = new ServiceDescriptor(stubService, SERVICE_NAME, MODULE_NAME, "FakeAssembly");
|
||||||
|
//Then
|
||||||
|
Assert.Equal(MODULE_NAME, serviceInfo.GetModuleName());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetAssemblyName_ServiceNotStarted_ReturnsSetAssemblyName()
|
||||||
|
{
|
||||||
|
//Given
|
||||||
|
const string ASSEMBLY_NAME = "FakeAssembly";
|
||||||
|
const string SERVICE_NAME = "GetAssemblyName_ServiceNotStarted_ReturnsSetAssemblyName";
|
||||||
|
IService stubService = new FakeService();
|
||||||
|
ServiceDescriptor serviceInfo = new ServiceDescriptor(stubService, SERVICE_NAME, "FakeModule", ASSEMBLY_NAME);
|
||||||
|
//Then
|
||||||
|
Assert.Equal(ASSEMBLY_NAME, serviceInfo.GetAssemblyName());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetServiceName_ServiceNotStartedSingleThread_ServiceNameReturned()
|
||||||
|
{
|
||||||
|
//Given
|
||||||
|
const string SERVICE_NAME = "GetServiceName_ServiceNotStartedSingleThread_ServiceNameReturned";
|
||||||
|
IService stubService = new FakeService();
|
||||||
|
//When
|
||||||
|
ServiceDescriptor serviceInfo = new ServiceDescriptor(stubService, SERVICE_NAME, "FakeModule", "FakeAssemblyName");
|
||||||
|
//Then
|
||||||
|
Assert.True(SERVICE_NAME.Equals(serviceInfo.ServiceName));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetLogBuffer_CommandWritten_CommandLogged()
|
||||||
|
{
|
||||||
|
//Given
|
||||||
|
const string CMD = "hello";
|
||||||
|
const string SERVICE_NAME = "GetLogBuffer_CommandWritten_CommandLogged";
|
||||||
|
IService stubService = new FakeService();
|
||||||
|
ServiceDescriptor serviceInfo = new ServiceDescriptor(stubService, SERVICE_NAME, "FakeModule", "FakeAssembly");
|
||||||
|
serviceInfo.Start();
|
||||||
|
//When
|
||||||
|
serviceInfo.ExecuteCommand(CMD);
|
||||||
|
//Then
|
||||||
|
using (MemoryStream mem = new MemoryStream(serviceInfo.GetLogBuffer()))
|
||||||
|
{
|
||||||
|
using (StreamReader reader = new StreamReader(mem))
|
||||||
|
{
|
||||||
|
Assert.Equal(CMD, reader.ReadLine());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,229 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using GameServiceWarden.Core.Module;
|
||||||
|
using GameServiceWarden.ModuleFramework;
|
||||||
|
using Xunit;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
[assembly: CollectionBehavior(DisableTestParallelization = true)]
|
||||||
|
namespace GameServiceWarden.Core.Tests.Modules
|
||||||
|
{
|
||||||
|
[CollectionDefinition("Service")]
|
||||||
|
public class ServiceManagerTest
|
||||||
|
{
|
||||||
|
public ServiceManagerTest(ITestOutputHelper output)
|
||||||
|
{
|
||||||
|
Logger.AddLogListener(new XUnitLogger(output));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateService_NewManager_NewServiceCreated()
|
||||||
|
{
|
||||||
|
//Given
|
||||||
|
const string ASSEMBLY_NAME = "FakeAssembly";
|
||||||
|
const string FAKE_SERVICE_NAME = "CreateService_NewManager_NewServiceCreated";
|
||||||
|
FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>();
|
||||||
|
FakePersistence<IReadOnlyDictionary<string, string>> stubPersistentServiceDictionary = new FakePersistence<IReadOnlyDictionary<string, string>>();
|
||||||
|
FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor();
|
||||||
|
ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary);
|
||||||
|
Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>();
|
||||||
|
IServiceModule stubServiceModule = new FakeServiceModule();
|
||||||
|
stubAssemblyModulesDictionary.Add(stubServiceModule.Name, stubServiceModule);
|
||||||
|
stubPersistentModuleDictionary.AddToPersistence(ASSEMBLY_NAME, stubAssemblyModulesDictionary);
|
||||||
|
//When
|
||||||
|
serviceManager.CreateService(FAKE_SERVICE_NAME, ASSEMBLY_NAME, stubServiceModule.Name);
|
||||||
|
//Then
|
||||||
|
Assert.True(FAKE_SERVICE_NAME.Equals(stubMonitor.GetLastDelta().service));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateService_OneService_ServiceDeleted()
|
||||||
|
{
|
||||||
|
//Given
|
||||||
|
const string ASSEMBLY_NAME = "FakeAssembly";
|
||||||
|
const string FAKE_SERVICE_NAME = "CreateService_OneService_ServiceDeleted";
|
||||||
|
FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>();
|
||||||
|
FakePersistence<IReadOnlyDictionary<string, string>> stubPersistentServiceDictionary = new FakePersistence<IReadOnlyDictionary<string, string>>();
|
||||||
|
FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor();
|
||||||
|
ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary);
|
||||||
|
Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>();
|
||||||
|
IServiceModule stubServiceModule = new FakeServiceModule();
|
||||||
|
stubAssemblyModulesDictionary.Add(stubServiceModule.Name, stubServiceModule);
|
||||||
|
stubPersistentModuleDictionary.AddToPersistence(ASSEMBLY_NAME, stubAssemblyModulesDictionary);
|
||||||
|
//When
|
||||||
|
serviceManager.CreateService(FAKE_SERVICE_NAME, ASSEMBLY_NAME, stubServiceModule.Name);
|
||||||
|
serviceManager.DeleteService(FAKE_SERVICE_NAME);
|
||||||
|
//Then
|
||||||
|
Assert.True(stubMonitor.GetLastDelta().subtract);
|
||||||
|
Assert.True(FAKE_SERVICE_NAME.Equals(stubMonitor.GetLastDelta().service));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetServiceNames_MultipleServices_AllCorrectNames()
|
||||||
|
{
|
||||||
|
//Given
|
||||||
|
const string ASSEMBLY_NAME = "FakeAssembly";
|
||||||
|
const string FAKE_SERVICE_PREFIX = "GetServiceNames_MultipleServices_AllCorrectNames_";
|
||||||
|
FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>();
|
||||||
|
FakePersistence<IReadOnlyDictionary<string, string>> stubPersistentServiceDictionary = new FakePersistence<IReadOnlyDictionary<string, string>>();
|
||||||
|
FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor();
|
||||||
|
ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary);
|
||||||
|
Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>();
|
||||||
|
IServiceModule stubServiceModule = new FakeServiceModule();
|
||||||
|
stubAssemblyModulesDictionary.Add(stubServiceModule.Name, stubServiceModule);
|
||||||
|
stubPersistentModuleDictionary.AddToPersistence(ASSEMBLY_NAME, stubAssemblyModulesDictionary);
|
||||||
|
//When
|
||||||
|
for (int i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
serviceManager.CreateService(FAKE_SERVICE_PREFIX + i, ASSEMBLY_NAME, stubServiceModule.Name);
|
||||||
|
}
|
||||||
|
//Then
|
||||||
|
for (int i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
Assert.Contains<string>(FAKE_SERVICE_PREFIX + i, serviceManager.GetServiceNames());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetServiceOptions_ThreeOptionService_CorrectOptions()
|
||||||
|
{
|
||||||
|
//Given
|
||||||
|
const string ASSEMBLY_NAME = "FakeAssembly";
|
||||||
|
const string FAKE_SERVICE_NAME = "GetServiceOptions_ThreeOptionService_CorrectOptions";
|
||||||
|
FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>();
|
||||||
|
FakePersistence<IReadOnlyDictionary<string, string>> stubPersistentServiceDictionary = new FakePersistence<IReadOnlyDictionary<string, string>>();
|
||||||
|
FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor();
|
||||||
|
ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary);
|
||||||
|
Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>();
|
||||||
|
IServiceModule stubServiceModule = new FakeServiceModule(
|
||||||
|
new FakeServiceConfigurable("A"),
|
||||||
|
new FakeServiceConfigurable("B"),
|
||||||
|
new FakeServiceConfigurable("C")
|
||||||
|
);
|
||||||
|
stubAssemblyModulesDictionary.Add(stubServiceModule.Name, stubServiceModule);
|
||||||
|
stubPersistentModuleDictionary.AddToPersistence(ASSEMBLY_NAME, stubAssemblyModulesDictionary);
|
||||||
|
//When
|
||||||
|
serviceManager.CreateService(FAKE_SERVICE_NAME, ASSEMBLY_NAME, stubServiceModule.Name);
|
||||||
|
//Then
|
||||||
|
Assert.Contains<string>("A", serviceManager.GetOptions()[FAKE_SERVICE_NAME].Keys);
|
||||||
|
Assert.Contains<string>("B", serviceManager.GetOptions()[FAKE_SERVICE_NAME].Keys);
|
||||||
|
Assert.Contains<string>("C", serviceManager.GetOptions()[FAKE_SERVICE_NAME].Keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SetandGetServiceOptionValue_OneOption_OptionChanged()
|
||||||
|
{
|
||||||
|
const string ASSEMBLY_NAME = "FakeAssembly";
|
||||||
|
const string FAKE_SERVICE_NAME = "SetandGetServiceOptionValue_OneOption_OptionChanged";
|
||||||
|
FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>();
|
||||||
|
FakePersistence<IReadOnlyDictionary<string, string>> stubPersistentServiceDictionary = new FakePersistence<IReadOnlyDictionary<string, string>>();
|
||||||
|
FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor();
|
||||||
|
ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary);
|
||||||
|
Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>();
|
||||||
|
IServiceModule stubServiceModule = new FakeServiceModule(
|
||||||
|
new FakeServiceConfigurable("A")
|
||||||
|
);
|
||||||
|
stubAssemblyModulesDictionary.Add(stubServiceModule.Name, stubServiceModule);
|
||||||
|
stubPersistentModuleDictionary.AddToPersistence(ASSEMBLY_NAME, stubAssemblyModulesDictionary);
|
||||||
|
//When
|
||||||
|
serviceManager.CreateService(FAKE_SERVICE_NAME, ASSEMBLY_NAME, stubServiceModule.Name);
|
||||||
|
serviceManager.SetServiceOptionValue(FAKE_SERVICE_NAME, "A", "Test");
|
||||||
|
//Then
|
||||||
|
Assert.True("Test".Equals(serviceManager.GetOptions()[FAKE_SERVICE_NAME]["A"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetServiceState_NotRunning_ReturnsNotRunningState()
|
||||||
|
{
|
||||||
|
//Given
|
||||||
|
const string ASSEMBLY_NAME = "FakeAssembly";
|
||||||
|
const string FAKE_SERVICE_NAME = "GetServiceState_NotRunning_ReturnsNotRunningState";
|
||||||
|
FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>();
|
||||||
|
FakePersistence<IReadOnlyDictionary<string, string>> stubPersistentServiceDictionary = new FakePersistence<IReadOnlyDictionary<string, string>>();
|
||||||
|
FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor();
|
||||||
|
ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary);
|
||||||
|
Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>();
|
||||||
|
IServiceModule stubServiceModule = new FakeServiceModule();
|
||||||
|
stubAssemblyModulesDictionary.Add(stubServiceModule.Name, stubServiceModule);
|
||||||
|
stubPersistentModuleDictionary.AddToPersistence(ASSEMBLY_NAME, stubAssemblyModulesDictionary);
|
||||||
|
//When
|
||||||
|
serviceManager.CreateService(FAKE_SERVICE_NAME, ASSEMBLY_NAME, stubServiceModule.Name);
|
||||||
|
//Then
|
||||||
|
Assert.DoesNotContain(FAKE_SERVICE_NAME, serviceManager.GetRunningServiceNames());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void StartService_NotStarted_SuccessfulStart()
|
||||||
|
{
|
||||||
|
//Given
|
||||||
|
const string ASSEMBLY_NAME = "FakeAssembly";
|
||||||
|
const string FAKE_SERVICE_NAME = "StartService_NotStarted_SuccessfulStart";
|
||||||
|
FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>();
|
||||||
|
FakePersistence<IReadOnlyDictionary<string, string>> stubPersistentServiceDictionary = new FakePersistence<IReadOnlyDictionary<string, string>>();
|
||||||
|
FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor();
|
||||||
|
ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary);
|
||||||
|
Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>();
|
||||||
|
IServiceModule stubServiceModule = new FakeServiceModule();
|
||||||
|
stubAssemblyModulesDictionary.Add(stubServiceModule.Name, stubServiceModule);
|
||||||
|
stubPersistentModuleDictionary.AddToPersistence(ASSEMBLY_NAME, stubAssemblyModulesDictionary);
|
||||||
|
//When
|
||||||
|
serviceManager.CreateService(FAKE_SERVICE_NAME, ASSEMBLY_NAME, stubServiceModule.Name);
|
||||||
|
serviceManager.StartService(FAKE_SERVICE_NAME);
|
||||||
|
//Then
|
||||||
|
Assert.Contains(FAKE_SERVICE_NAME, serviceManager.GetRunningServiceNames());
|
||||||
|
serviceManager.StopService(FAKE_SERVICE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void StopService_ServiceStartedThenStopped_StateUpdated()
|
||||||
|
{
|
||||||
|
//Given
|
||||||
|
const string ASSEMBLY_NAME = "FakeAssembly";
|
||||||
|
const string FAKE_SERVICE_NAME = "StopService_ServiceStartedThenStopped_StateUpdated";
|
||||||
|
FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>();
|
||||||
|
FakePersistence<IReadOnlyDictionary<string, string>> stubPersistentServiceDictionary = new FakePersistence<IReadOnlyDictionary<string, string>>();
|
||||||
|
FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor();
|
||||||
|
ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary);
|
||||||
|
Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>();
|
||||||
|
IServiceModule stubServiceModule = new FakeServiceModule();
|
||||||
|
stubAssemblyModulesDictionary.Add(stubServiceModule.Name, stubServiceModule);
|
||||||
|
stubPersistentModuleDictionary.AddToPersistence(ASSEMBLY_NAME, stubAssemblyModulesDictionary);
|
||||||
|
//When
|
||||||
|
serviceManager.CreateService(FAKE_SERVICE_NAME, ASSEMBLY_NAME, stubServiceModule.Name);
|
||||||
|
serviceManager.StartService(FAKE_SERVICE_NAME);
|
||||||
|
serviceManager.StopService(FAKE_SERVICE_NAME);
|
||||||
|
//Then
|
||||||
|
Assert.DoesNotContain(FAKE_SERVICE_NAME, serviceManager.GetRunningServiceNames());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ExecuteCommand_ServiceStarted_CommandLogged()
|
||||||
|
{
|
||||||
|
//Given
|
||||||
|
const string ASSEMBLY_NAME = "FakeAssembly";
|
||||||
|
const string FAKE_SERVICE_NAME = "ExecuteCommand_ServiceStarted_CommandLogged";
|
||||||
|
const string COMMAND = "TEST";
|
||||||
|
FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>();
|
||||||
|
FakePersistence<IReadOnlyDictionary<string, string>> stubPersistentServiceDictionary = new FakePersistence<IReadOnlyDictionary<string, string>>();
|
||||||
|
FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor();
|
||||||
|
ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary);
|
||||||
|
Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>();
|
||||||
|
IServiceModule stubServiceModule = new FakeServiceModule();
|
||||||
|
stubAssemblyModulesDictionary.Add(stubServiceModule.Name, stubServiceModule);
|
||||||
|
stubPersistentModuleDictionary.AddToPersistence(ASSEMBLY_NAME, stubAssemblyModulesDictionary);
|
||||||
|
//When
|
||||||
|
serviceManager.CreateService(FAKE_SERVICE_NAME, ASSEMBLY_NAME, stubServiceModule.Name);
|
||||||
|
serviceManager.StartService(FAKE_SERVICE_NAME);
|
||||||
|
serviceManager.ExecuteCommand(FAKE_SERVICE_NAME, COMMAND);
|
||||||
|
//Then
|
||||||
|
using (MemoryStream mem = new MemoryStream(serviceManager.GetLogBuffers()[FAKE_SERVICE_NAME]))
|
||||||
|
{
|
||||||
|
using (StreamReader reader = new StreamReader(mem))
|
||||||
|
{
|
||||||
|
Assert.Equal(COMMAND, reader.ReadLine());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
serviceManager.StopService(FAKE_SERVICE_NAME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,100 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using GameServiceWarden.Core.Module;
|
||||||
|
using GameServiceWarden.Core.Persistence;
|
||||||
|
using GameServiceWarden.Core.Tests.Modules;
|
||||||
|
using GameServiceWarden.ModuleFramework;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.Core.Tests.Persistence
|
||||||
|
{
|
||||||
|
public class ServiceDescriptorPersistenceTest
|
||||||
|
{
|
||||||
|
//MethodTested_ScenarioTested_ExpectedBehavior
|
||||||
|
[Fact]
|
||||||
|
public void GetPathForKey_PathGen_ExpectedPathResult()
|
||||||
|
{
|
||||||
|
//Given
|
||||||
|
const string TEST_DIR = "services";
|
||||||
|
const string MODULE_NAME = "fake_module";
|
||||||
|
const string ASSEMBLY_NAME = "fake_assembly";
|
||||||
|
const string SERVICE_NAME = "fake_service";
|
||||||
|
FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubModulesPersistence = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>();
|
||||||
|
Dictionary<string, IServiceModule> stubAssemblyDict = new Dictionary<string, IServiceModule>();
|
||||||
|
FakeServiceModule stubServiceModule = new FakeServiceModule();
|
||||||
|
|
||||||
|
stubAssemblyDict[MODULE_NAME] = stubServiceModule;
|
||||||
|
stubModulesPersistence[ASSEMBLY_NAME] = stubAssemblyDict;
|
||||||
|
|
||||||
|
ServiceDescriptorPersistence persistedServices = new ServiceDescriptorPersistence(TEST_DIR);
|
||||||
|
|
||||||
|
//Then
|
||||||
|
Assert.True(persistedServices.GetPathForKey(SERVICE_NAME).Equals(Path.Combine(TEST_DIR, SERVICE_NAME)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Save_SavingService_FileCreated()
|
||||||
|
{
|
||||||
|
//Given
|
||||||
|
const string TEST_DIR = "services";
|
||||||
|
const string MODULE_NAME = "fake_module";
|
||||||
|
const string ASSEMBLY_NAME = "fake_assembly";
|
||||||
|
const string SERVICE_NAME = "fake_service";
|
||||||
|
FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubModulesPersistence = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>();
|
||||||
|
Dictionary<string, IServiceModule> stubAssemblyDict = new Dictionary<string, IServiceModule>();
|
||||||
|
FakeServiceModule stubServiceModule = new FakeServiceModule();
|
||||||
|
|
||||||
|
stubAssemblyDict[MODULE_NAME] = stubServiceModule;
|
||||||
|
stubModulesPersistence[ASSEMBLY_NAME] = stubAssemblyDict;
|
||||||
|
|
||||||
|
ServiceDescriptorPersistence persistedServiceInfos = new ServiceDescriptorPersistence(TEST_DIR);
|
||||||
|
|
||||||
|
ServiceDescriptor stubServiceInfo = new ServiceDescriptor(stubModulesPersistence[ASSEMBLY_NAME][MODULE_NAME].InstantiateService(persistedServiceInfos.GetPathForKey(SERVICE_NAME)), SERVICE_NAME, MODULE_NAME, ASSEMBLY_NAME);
|
||||||
|
//When
|
||||||
|
Dictionary<string, string> info = new Dictionary<string, string>();
|
||||||
|
info[ServiceDescriptor.MODULE_PROPERTY] = MODULE_NAME;
|
||||||
|
info[ServiceDescriptor.ASSEMBLY_PROPERTY] = ASSEMBLY_NAME;
|
||||||
|
persistedServiceInfos[SERVICE_NAME] = info;
|
||||||
|
//Then
|
||||||
|
Assert.True(Directory.Exists(TEST_DIR));
|
||||||
|
Assert.True(Directory.Exists(persistedServiceInfos.GetPathForKey(SERVICE_NAME)));
|
||||||
|
string[] files = Directory.GetFiles(persistedServiceInfos.GetPathForKey(SERVICE_NAME));
|
||||||
|
Assert.True(files.Length == 1);
|
||||||
|
Assert.StartsWith(SERVICE_NAME, Path.GetFileName(files[0]));
|
||||||
|
|
||||||
|
Directory.Delete(TEST_DIR, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Save_ReadingService_MetadataRead()
|
||||||
|
{
|
||||||
|
//Given
|
||||||
|
const string TEST_DIR = "services";
|
||||||
|
const string MODULE_NAME = "fake_module";
|
||||||
|
const string ASSEMBLY_NAME = "fake_assembly";
|
||||||
|
const string SERVICE_NAME = "fake_service";
|
||||||
|
FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubModulesPersistence = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>();
|
||||||
|
Dictionary<string, IServiceModule> stubAssemblyDict = new Dictionary<string, IServiceModule>();
|
||||||
|
FakeServiceModule stubServiceModule = new FakeServiceModule();
|
||||||
|
|
||||||
|
stubAssemblyDict[MODULE_NAME] = stubServiceModule;
|
||||||
|
stubModulesPersistence[ASSEMBLY_NAME] = stubAssemblyDict;
|
||||||
|
|
||||||
|
ServiceDescriptorPersistence persistedServices = new ServiceDescriptorPersistence(TEST_DIR);
|
||||||
|
|
||||||
|
Dictionary<string, string> info = new Dictionary<string, string>();
|
||||||
|
info[ServiceDescriptor.MODULE_PROPERTY] = MODULE_NAME;
|
||||||
|
info[ServiceDescriptor.ASSEMBLY_PROPERTY] = ASSEMBLY_NAME;
|
||||||
|
persistedServices[SERVICE_NAME] = info;
|
||||||
|
//When
|
||||||
|
IReadOnlyDictionary<string, string> loadedServiceInfo = persistedServices[SERVICE_NAME];
|
||||||
|
string loadedAssemblyName = loadedServiceInfo[ServiceDescriptor.ASSEMBLY_PROPERTY];
|
||||||
|
string loadedModuleName = loadedServiceInfo[ServiceDescriptor.MODULE_PROPERTY];
|
||||||
|
//Then
|
||||||
|
Assert.True(loadedModuleName.Equals(MODULE_NAME));
|
||||||
|
Assert.True(loadedAssemblyName.Equals(ASSEMBLY_NAME));
|
||||||
|
|
||||||
|
Directory.Delete(TEST_DIR, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
using System;
|
||||||
|
using GameServiceWarden.Core.UI;
|
||||||
|
using Xunit;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.Core.Tests.UI
|
||||||
|
{
|
||||||
|
public class IPCMediatorTest
|
||||||
|
{
|
||||||
|
public IPCMediatorTest(ITestOutputHelper output)
|
||||||
|
{
|
||||||
|
Logger.AddLogListener(new XUnitLogger(output));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Open_Closed_Opened()
|
||||||
|
{
|
||||||
|
//Given
|
||||||
|
const string NAME = "Open_Closed_Opened";
|
||||||
|
IPCMediator mediator = new IPCMediator(NAME);
|
||||||
|
//When
|
||||||
|
mediator.Open();
|
||||||
|
//Then
|
||||||
|
Assert.True(mediator.IsRunning);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Open_AlreadyOpened_Exception()
|
||||||
|
{
|
||||||
|
//Given
|
||||||
|
const string NAME = "Open_AlreadyOpened_Exception";
|
||||||
|
IPCMediator mediator = new IPCMediator(NAME);
|
||||||
|
//When
|
||||||
|
mediator.Open();
|
||||||
|
//Then
|
||||||
|
Assert.Throws<InvalidOperationException>(() => mediator.Open());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Close_Opened_Closed()
|
||||||
|
{
|
||||||
|
//Given
|
||||||
|
const string NAME = "Close_Opened_Closed";
|
||||||
|
IPCMediator mediator = new IPCMediator(NAME);
|
||||||
|
//When
|
||||||
|
mediator.Open();
|
||||||
|
//Then
|
||||||
|
Assert.True(mediator.IsRunning);
|
||||||
|
mediator.Close();
|
||||||
|
Assert.False(mediator.IsRunning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
using System;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.Core.Tests
|
||||||
|
{
|
||||||
|
public class XUnitLogger : ILogReceiver
|
||||||
|
{
|
||||||
|
public LogLevel Level => LogLevel.Debug;
|
||||||
|
|
||||||
|
public string Identifier => GetType().Name;
|
||||||
|
|
||||||
|
private ITestOutputHelper outputHelper;
|
||||||
|
|
||||||
|
public XUnitLogger(ITestOutputHelper output)
|
||||||
|
{
|
||||||
|
this.outputHelper = output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Flush()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LogMessage(string message, DateTime time, LogLevel level)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
outputHelper.WriteLine($"[{time.ToShortTimeString()}][{level.ToString()}]: {message}");
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException) { };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.Core.Collection
|
||||||
|
{
|
||||||
|
public class LRUCache<K, V> : IEnumerable<V>
|
||||||
|
{
|
||||||
|
private class Node
|
||||||
|
{
|
||||||
|
public K key;
|
||||||
|
public V value;
|
||||||
|
public Node front;
|
||||||
|
public Node back;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Size {get { return size; } }
|
||||||
|
public int Count {get { return valueDictionary.Count; } }
|
||||||
|
private readonly int size;
|
||||||
|
private Node top;
|
||||||
|
private Node bottom;
|
||||||
|
private Dictionary<K, Node> valueDictionary;
|
||||||
|
private Action<V> cleanupAction;
|
||||||
|
|
||||||
|
public LRUCache(int size = 100, Action<V> cleanup = null)
|
||||||
|
{
|
||||||
|
this.size = size;
|
||||||
|
valueDictionary = new Dictionary<K, Node>(size);
|
||||||
|
this.cleanupAction = cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MoveToTop(K key) {
|
||||||
|
Node node = valueDictionary[key];
|
||||||
|
if (node != null && top != node) {
|
||||||
|
node.front.back = node.back;
|
||||||
|
node.back = top;
|
||||||
|
node.front = null;
|
||||||
|
top = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node AddToTop(K key, V value) {
|
||||||
|
Node node = new Node();
|
||||||
|
node.front = null;
|
||||||
|
node.back = top;
|
||||||
|
node.value = value;
|
||||||
|
node.key = key;
|
||||||
|
top = node;
|
||||||
|
if (bottom == null) {
|
||||||
|
bottom = node;
|
||||||
|
} else if (valueDictionary.Count == Size) {
|
||||||
|
valueDictionary.Remove(bottom.key);
|
||||||
|
cleanupAction?.Invoke(bottom.value);
|
||||||
|
bottom = bottom.front;
|
||||||
|
}
|
||||||
|
valueDictionary[key] = node;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public V Use(K key, Func<V> generate) {
|
||||||
|
if (generate == null) throw new ArgumentNullException("generate");
|
||||||
|
Node value = null;
|
||||||
|
if (valueDictionary.ContainsKey(key)) {
|
||||||
|
value = valueDictionary[key];
|
||||||
|
MoveToTop(key);
|
||||||
|
} else {
|
||||||
|
value = AddToTop(key, generate());
|
||||||
|
}
|
||||||
|
return value.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsCached(K key) {
|
||||||
|
return valueDictionary.ContainsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear() {
|
||||||
|
top = null;
|
||||||
|
bottom = null;
|
||||||
|
valueDictionary.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<V> GetEnumerator()
|
||||||
|
{
|
||||||
|
foreach (Node node in valueDictionary.Values)
|
||||||
|
{
|
||||||
|
yield return node.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\GameServiceWarden.ModuleFramework\GameServiceWarden.ModuleFramework.csproj" />
|
||||||
|
<ProjectReference Include="..\GameServiceWarden.InteractionAPI\GameServiceWarden.InteractionAPI.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.Core.Module.Exceptions
|
||||||
|
{
|
||||||
|
[System.Serializable]
|
||||||
|
public class ModuleLoadException : Exception
|
||||||
|
{
|
||||||
|
public ModuleLoadException() { }
|
||||||
|
public ModuleLoadException(string message) : base(message) { }
|
||||||
|
public ModuleLoadException(string message, Exception inner) : base(message, inner) { }
|
||||||
|
protected ModuleLoadException(
|
||||||
|
SerializationInfo info,
|
||||||
|
StreamingContext context) : base(info, context) { }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
namespace GameServiceWarden.Core.Module.Exceptions
|
||||||
|
{
|
||||||
|
[System.Serializable]
|
||||||
|
public class ServiceInitializationException : System.Exception
|
||||||
|
{
|
||||||
|
public ServiceInitializationException() { }
|
||||||
|
public ServiceInitializationException(string message) : base(message) { }
|
||||||
|
public ServiceInitializationException(string message, System.Exception inner) : base(message, inner) { }
|
||||||
|
protected ServiceInitializationException(
|
||||||
|
System.Runtime.Serialization.SerializationInfo info,
|
||||||
|
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
using GameServiceWarden.InteractionAPI.Module;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.Core.Module
|
||||||
|
{
|
||||||
|
public interface IServiceManagerActionExecuter
|
||||||
|
{
|
||||||
|
void ExecuteAction(ServiceManagerAction action);
|
||||||
|
void View();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
using GameServiceWarden.InteractionAPI.Module;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.Core.Module
|
||||||
|
{
|
||||||
|
public interface IServiceManagerMonitor
|
||||||
|
{
|
||||||
|
void Present(ServiceManagerTotal state);
|
||||||
|
void Present(ServiceManagerDelta delta);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.Loader;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.Core.Module
|
||||||
|
{
|
||||||
|
class ModuleLoadContext : AssemblyLoadContext
|
||||||
|
{
|
||||||
|
private readonly 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using GameServiceWarden.Core.Module.Exceptions;
|
||||||
|
using GameServiceWarden.ModuleFramework;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.Core.Module
|
||||||
|
{
|
||||||
|
public class ModuleLoader //Gateway
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Loads an extension module.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path to the module.</param>
|
||||||
|
/// <returns>An </<see cref="IEnumerable{IServiceModule}"/> from the given module.</returns>
|
||||||
|
/// <exception cref="NoServiceableFoundException">When the module requested to be loaded does not contain any public <see cref="IService"/> classes.</exception>
|
||||||
|
public IEnumerable<IServiceModule> 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{IServiceModule}}"/> where the key is a <see cref="string"/> that is the associated path.</returns>
|
||||||
|
public Dictionary<string, IEnumerable<IServiceModule>> LoadAllModules(IEnumerable<string> paths)
|
||||||
|
{
|
||||||
|
Dictionary<string, IEnumerable<IServiceModule>> res = new Dictionary<string, IEnumerable<IServiceModule>>();
|
||||||
|
foreach (string path in paths)
|
||||||
|
{
|
||||||
|
res.Add(path, LoadModules(path));
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Assembly loadAssembly(string path)
|
||||||
|
{
|
||||||
|
ModuleLoadContext moduleLoadContext = new ModuleLoadContext(path);
|
||||||
|
return moduleLoadContext.LoadFromAssemblyPath(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<IServiceModule> instantiateServiceable(Assembly assembly)
|
||||||
|
{
|
||||||
|
int serviceableCount = 0;
|
||||||
|
foreach (Type type in assembly.GetExportedTypes())
|
||||||
|
{
|
||||||
|
if (typeof(IServiceModule).IsAssignableFrom(type))
|
||||||
|
{
|
||||||
|
IServiceModule res = Activator.CreateInstance(type) as IServiceModule;
|
||||||
|
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 ModuleLoadException(
|
||||||
|
$"No public classes in {assembly} from {assembly.Location} implemented {typeof(IService).FullName}." +
|
||||||
|
$"Detected types: {types}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,155 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Pipes;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using GameServiceWarden.Core.Module.Exceptions;
|
||||||
|
using GameServiceWarden.ModuleFramework;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
|
||||||
|
//TODO Update UML
|
||||||
|
namespace GameServiceWarden.Core.Module
|
||||||
|
{
|
||||||
|
public class ServiceDescriptor //entity
|
||||||
|
{
|
||||||
|
public const string MODULE_PROPERTY = "SERVICE_MODULE";
|
||||||
|
public const string ASSEMBLY_PROPERTY = "SERVICE_ASSEMBLY";
|
||||||
|
private const int TIMEOUT = 1000;
|
||||||
|
/// <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; } }
|
||||||
|
private readonly string serviceName;
|
||||||
|
private volatile ServiceState state;
|
||||||
|
private readonly IService service;
|
||||||
|
private string moduleName;
|
||||||
|
private readonly string assemblyName;
|
||||||
|
/// <summary>
|
||||||
|
/// Name of module this service uses.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IReadOnlyDictionary<string, IServiceConfigurable> configurables;
|
||||||
|
public event EventHandler<ServiceState> ServiceStateChangeEvent;
|
||||||
|
public event EventHandler<string> LogUpdateEvent;
|
||||||
|
public ServiceDescriptor(IService service, string serviceName, string moduleName, string assemblyName)
|
||||||
|
{
|
||||||
|
this.service = service ?? throw new ArgumentNullException("service");
|
||||||
|
this.moduleName = moduleName ?? throw new ArgumentNullException("moduleName");
|
||||||
|
this.assemblyName = assemblyName ?? throw new ArgumentNullException("assemblyName");
|
||||||
|
this.serviceName = serviceName ?? throw new ArgumentNullException("serviceName");
|
||||||
|
this.service.StateChangeEvent += OnServiceStateChange;
|
||||||
|
this.service.UpdateLogEvent += OnLogUpdate;
|
||||||
|
|
||||||
|
Dictionary<string, IServiceConfigurable> tempConfigurables = new Dictionary<string, IServiceConfigurable>();
|
||||||
|
foreach (IServiceConfigurable configurable in service.Configurables)
|
||||||
|
{
|
||||||
|
tempConfigurables.Add(configurable.OptionName, configurable);
|
||||||
|
}
|
||||||
|
this.configurables = new ReadOnlyDictionary<string, IServiceConfigurable>(tempConfigurables);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts this service.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="InvalidOperationException">Is thrown when the service is already running.</exception>
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
Logger.Log($"\"{ServiceName}\" is starting.");
|
||||||
|
if (state == ServiceState.Running) throw new InvalidOperationException("Service instance already running.");
|
||||||
|
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TIMEOUT);
|
||||||
|
Task initializationTask = Task.Run(() => service.InitializeService(), cancellationTokenSource.Token);
|
||||||
|
try {
|
||||||
|
initializationTask.Wait();
|
||||||
|
} catch (AggregateException a) {
|
||||||
|
a.Handle((e) => e is TaskCanceledException);
|
||||||
|
}
|
||||||
|
cancellationTokenSource.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops the service.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="InvalidOperationException">Is thrown when the is not running.</exception>
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
if (state != ServiceState.Running) throw new InvalidOperationException("Service instance not running.");
|
||||||
|
Logger.Log($"\"{ServiceName}\" is stopping.");
|
||||||
|
service.ElegantShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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)
|
||||||
|
{
|
||||||
|
Logger.Log($"\"{ServiceName}\" is executing command \"{command}\".", LogLevel.Debug);
|
||||||
|
if (state != ServiceState.Running) throw new InvalidOperationException("Service instance not running.");
|
||||||
|
service.ExecuteCommand(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the possible <see cref="IServiceConfigurable"/>'s names for this service.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A <see cref="ISet{string}"/> returned where the string is the option's name.</returns>
|
||||||
|
public ISet<string> GetConfigurableOptions()
|
||||||
|
{
|
||||||
|
return new HashSet<string>(this.configurables.Keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetConfigurableValue(string configurationName, string value)
|
||||||
|
{
|
||||||
|
if (!this.configurables.ContainsKey(configurationName)) throw new KeyNotFoundException($"Unable to find option with name \"{configurationName}\".");
|
||||||
|
return this.configurables[configurationName].SetValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetConfigurableValue(string configurationName)
|
||||||
|
{
|
||||||
|
if (!this.configurables.ContainsKey(configurationName)) throw new KeyNotFoundException($"Unable to find option with name \"{configurationName}\".");
|
||||||
|
return this.configurables[configurationName].GetValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceState GetServiceState()
|
||||||
|
{
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetServiceName()
|
||||||
|
{
|
||||||
|
return serviceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetModuleName()
|
||||||
|
{
|
||||||
|
return moduleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <returns>The name of assembly this module is contained in.</returns>
|
||||||
|
public string GetAssemblyName()
|
||||||
|
{
|
||||||
|
return assemblyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] GetLogBuffer() {
|
||||||
|
if (state == ServiceState.Stopped) throw new InvalidOperationException("Cannot get log of service that is not running.");
|
||||||
|
return service.GetLogBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnServiceStateChange(object sender, ServiceState state)
|
||||||
|
{
|
||||||
|
this.state = state;
|
||||||
|
Logger.Log($"The service \"{ServiceName}\" is changing states to {this.state}.", LogLevel.Debug);
|
||||||
|
ServiceStateChangeEvent?.Invoke(this, this.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLogUpdate(object sender, string log) {
|
||||||
|
LogUpdateEvent?.Invoke(this, log);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,247 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using GameServiceWarden.InteractionAPI.Module;
|
||||||
|
using GameServiceWarden.Core.Persistence;
|
||||||
|
using GameServiceWarden.ModuleFramework;
|
||||||
|
using GameServiceWarden.Core.Collection;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.Core.Module
|
||||||
|
{
|
||||||
|
public class ServiceManager : IServiceManagerActionExecuter
|
||||||
|
{
|
||||||
|
private IServiceManagerMonitor managerMonitor;
|
||||||
|
private readonly ConcurrentDictionary<string, ServiceDescriptor> running;
|
||||||
|
private readonly IPersistent<IReadOnlyDictionary<string, string>> services;
|
||||||
|
private readonly IReadOnlyPersistent<IReadOnlyDictionary<string, IServiceModule>> modules;
|
||||||
|
private readonly LRUCache<string, ServiceDescriptor> descriptorCache;
|
||||||
|
|
||||||
|
public ServiceManager(IServiceManagerMonitor actionMonitor, IPersistent<IReadOnlyDictionary<string, string>> services, IReadOnlyPersistent<IReadOnlyDictionary<string, IServiceModule>> modules)
|
||||||
|
{
|
||||||
|
this.services = services ?? throw new ArgumentNullException("services");
|
||||||
|
this.modules = modules ?? throw new ArgumentNullException("modules");
|
||||||
|
this.managerMonitor = actionMonitor ?? throw new ArgumentNullException("actionMonitor");
|
||||||
|
this.running = new ConcurrentDictionary<string, ServiceDescriptor>();
|
||||||
|
this.descriptorCache = new LRUCache<string, ServiceDescriptor>(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateService(string serviceName, string assemblyName, string moduleName)
|
||||||
|
{
|
||||||
|
if (!this.modules.ContainsKey(assemblyName)) throw new KeyNotFoundException($"No file \"{assemblyName}\" found.");
|
||||||
|
IReadOnlyDictionary<string, IServiceModule> assemblyModules = this.modules[assemblyName];
|
||||||
|
if (services.ContainsKey(serviceName)) throw new ArgumentException($"Service of Name \"{serviceName}\" already exists.");
|
||||||
|
Dictionary<string, string> data = new Dictionary<string, string>();
|
||||||
|
data[ServiceDescriptor.ASSEMBLY_PROPERTY] = assemblyName;
|
||||||
|
data[ServiceDescriptor.MODULE_PROPERTY] = moduleName;
|
||||||
|
services.AddToPersistence(serviceName, data);
|
||||||
|
ServiceManagerDelta managerState = new ServiceManagerDelta();
|
||||||
|
managerState.subtract = false;
|
||||||
|
managerState.service = serviceName;
|
||||||
|
managerMonitor.Present(managerState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteService(string serviceName)
|
||||||
|
{
|
||||||
|
if (!services.ContainsKey(serviceName)) throw new KeyNotFoundException($"Service under name \"{serviceName}\" not found.");
|
||||||
|
if (running.ContainsKey(serviceName)) running[serviceName].Stop();
|
||||||
|
services.Delete(serviceName);
|
||||||
|
ServiceManagerDelta managerState = new ServiceManagerDelta();
|
||||||
|
managerState.subtract = true;
|
||||||
|
managerState.service = serviceName;
|
||||||
|
managerMonitor.Present(managerState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> GetModuleNames()
|
||||||
|
{
|
||||||
|
ServiceManagerTotal managerState = new ServiceManagerTotal();
|
||||||
|
managerState.modules = modules.Keys.ToImmutableArray();
|
||||||
|
managerMonitor.Present(managerState);
|
||||||
|
return modules.Keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> GetServiceNames()
|
||||||
|
{
|
||||||
|
ServiceManagerTotal managerState = new ServiceManagerTotal();
|
||||||
|
managerState.services = services.Keys.ToImmutableArray();
|
||||||
|
managerMonitor.Present(managerState);
|
||||||
|
return services.Keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> GetRunningServiceNames() {
|
||||||
|
ServiceManagerTotal managerState = new ServiceManagerTotal();
|
||||||
|
managerState.running = running.Keys.ToImmutableArray();
|
||||||
|
managerMonitor.Present(managerState);
|
||||||
|
return running.Keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<string> GetServiceOptions(string serviceName)
|
||||||
|
{
|
||||||
|
if (!services.ContainsKey(serviceName)) throw new KeyNotFoundException($"Service under name \"{serviceName}\" not found.");
|
||||||
|
IReadOnlyDictionary<string, string> info = services[serviceName];
|
||||||
|
ServiceDescriptor service = descriptorCache.Use(serviceName, () => GenerateDescriptor(serviceName, info[ServiceDescriptor.ASSEMBLY_PROPERTY], info[ServiceDescriptor.MODULE_PROPERTY]));
|
||||||
|
return service.GetConfigurableOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetServiceOptionValue(string serviceName, string optionName)
|
||||||
|
{
|
||||||
|
if (!services.ContainsKey(serviceName)) throw new KeyNotFoundException($"Service under name \"{serviceName}\" not found.");
|
||||||
|
IReadOnlyDictionary<string, string> info = services[serviceName];
|
||||||
|
ServiceDescriptor service = descriptorCache.Use(serviceName, () => GenerateDescriptor(serviceName, info[ServiceDescriptor.ASSEMBLY_PROPERTY], info[ServiceDescriptor.MODULE_PROPERTY]));
|
||||||
|
if (!service.GetConfigurableOptions().Contains(optionName)) throw new KeyNotFoundException($"Option \"{optionName}\" for service \"{serviceName}\" not found.");
|
||||||
|
return service.GetConfigurableValue(optionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> GetOptions() {
|
||||||
|
ServiceManagerTotal managerState = new ServiceManagerTotal();
|
||||||
|
Dictionary<string, IReadOnlyDictionary<string, string>> serviceOptions = new Dictionary<string, IReadOnlyDictionary<string, string>>();
|
||||||
|
foreach (string service in GetServiceNames())
|
||||||
|
{
|
||||||
|
Dictionary<string, string> optionsOfService = new Dictionary<string, string>();
|
||||||
|
foreach (string option in GetServiceOptions(service))
|
||||||
|
{
|
||||||
|
optionsOfService.Add(option, GetServiceOptionValue(service, option));
|
||||||
|
}
|
||||||
|
serviceOptions.Add(service, optionsOfService);
|
||||||
|
}
|
||||||
|
managerState.serviceOptions = serviceOptions;
|
||||||
|
managerMonitor.Present(managerState);
|
||||||
|
return serviceOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetServiceOptionValue(string serviceName, string optionName, string value)
|
||||||
|
{
|
||||||
|
if (!services.ContainsKey(serviceName)) throw new KeyNotFoundException($"Service under name \"{serviceName}\" not found.");
|
||||||
|
IReadOnlyDictionary<string, string> info = services[serviceName];
|
||||||
|
ServiceDescriptor service = descriptorCache.Use(serviceName, () => GenerateDescriptor(serviceName, info[ServiceDescriptor.ASSEMBLY_PROPERTY], info[ServiceDescriptor.MODULE_PROPERTY]));
|
||||||
|
if (!service.GetConfigurableOptions().Contains(optionName)) throw new KeyNotFoundException($"Option \"{optionName}\" for service \"{serviceName}\" not found.");
|
||||||
|
ServiceManagerDelta managerState = new ServiceManagerDelta();
|
||||||
|
if (service.SetConfigurableValue(optionName, value)) {
|
||||||
|
managerState.optionName = optionName;
|
||||||
|
managerState.optionValue = GetServiceOptionValue(serviceName, optionName);
|
||||||
|
managerMonitor.Present(managerState);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartService(string serviceName)
|
||||||
|
{
|
||||||
|
if (!services.ContainsKey(serviceName)) throw new KeyNotFoundException($"Service under name \"{serviceName}\" not found.");
|
||||||
|
if (running.ContainsKey(serviceName)) throw new InvalidOperationException($"Service under name \"{serviceName}\" is already running.");
|
||||||
|
IReadOnlyDictionary<string, string> info = services[serviceName];
|
||||||
|
ServiceDescriptor service = descriptorCache.Use(serviceName, () => GenerateDescriptor(serviceName, info[ServiceDescriptor.ASSEMBLY_PROPERTY], info[ServiceDescriptor.MODULE_PROPERTY]));
|
||||||
|
service.ServiceStateChangeEvent += OnServiceStateChange;
|
||||||
|
service.LogUpdateEvent += OnLogUpdated;
|
||||||
|
service.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopService(string serviceName)
|
||||||
|
{
|
||||||
|
if (!running.ContainsKey(serviceName)) throw new InvalidOperationException($"Service under name \"{serviceName}\" is not running.");
|
||||||
|
running[serviceName].Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ExecuteCommand(string serviceName, string command)
|
||||||
|
{
|
||||||
|
if (!running.ContainsKey(serviceName)) throw new InvalidOperationException($"Service under name \"{serviceName}\" is not running.");
|
||||||
|
running[serviceName].ExecuteCommand(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] GetServiceLogBuffer(string serviceName) {
|
||||||
|
if (!running.ContainsKey(serviceName)) throw new InvalidOperationException($"Service under name \"{serviceName}\" is not running and therefore, does not have a log.");
|
||||||
|
IReadOnlyDictionary<string, string> info = services[serviceName];
|
||||||
|
ServiceDescriptor service = descriptorCache.Use(serviceName, () => GenerateDescriptor(serviceName, info[ServiceDescriptor.ASSEMBLY_PROPERTY], info[ServiceDescriptor.MODULE_PROPERTY]));
|
||||||
|
return service.GetLogBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<string, byte[]> GetLogBuffers() {
|
||||||
|
Dictionary<string, byte[]> logs = new Dictionary<string, byte[]>();
|
||||||
|
foreach (string service in running.Keys)
|
||||||
|
{
|
||||||
|
logs.Add(service, running[service].GetLogBuffer());
|
||||||
|
}
|
||||||
|
ServiceManagerTotal managerState = new ServiceManagerTotal();
|
||||||
|
managerState.logs = logs;
|
||||||
|
managerMonitor.Present(managerState);
|
||||||
|
return logs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServiceDescriptor GenerateDescriptor(string name, string assembly, string module) {
|
||||||
|
return new ServiceDescriptor(modules[assembly][module].InstantiateService(services.GetPathForKey(name)), name, module, assembly);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnServiceStateChange(object sender, ServiceState state) {
|
||||||
|
ServiceDescriptor serviceInfo = (ServiceDescriptor)sender;
|
||||||
|
ServiceManagerDelta managerChange = new ServiceManagerDelta();
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case ServiceState.Running:
|
||||||
|
if (running.TryAdd(serviceInfo.ServiceName, serviceInfo)) {
|
||||||
|
managerChange.running = serviceInfo.ServiceName;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ServiceState.Stopped:
|
||||||
|
ServiceDescriptor removed;
|
||||||
|
if (running.TryRemove(serviceInfo.ServiceName, out removed)) {
|
||||||
|
removed.ServiceStateChangeEvent -= OnServiceStateChange;
|
||||||
|
removed.LogUpdateEvent -= OnLogUpdated;
|
||||||
|
Dictionary<string, string> removedInfo = new Dictionary<string, string>();
|
||||||
|
removedInfo[ServiceDescriptor.ASSEMBLY_PROPERTY] = removed.GetAssemblyName();
|
||||||
|
removedInfo[ServiceDescriptor.MODULE_PROPERTY] = removed.GetModuleName();
|
||||||
|
services[serviceInfo.ServiceName] = removedInfo;
|
||||||
|
managerChange.subtract = true;
|
||||||
|
managerChange.running = serviceInfo.ServiceName;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
managerMonitor.Present(managerChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnLogUpdated(object sender, string update) {
|
||||||
|
ServiceDescriptor service = (ServiceDescriptor)sender;
|
||||||
|
ServiceManagerDelta delta = new ServiceManagerDelta();
|
||||||
|
delta.logs = Encoding.UTF8.GetBytes(update);
|
||||||
|
managerMonitor.Present(delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ExecuteAction(ServiceManagerAction action)
|
||||||
|
{
|
||||||
|
switch (action.action)
|
||||||
|
{
|
||||||
|
case ServiceManagerAction.Type.CreateService:
|
||||||
|
CreateService(action.serviceName, action.assemblyName, action.moduleName);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ServiceManagerAction.Type.DeleteService:
|
||||||
|
DeleteService(action.serviceName);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ServiceManagerAction.Type.ExecuteCommand:
|
||||||
|
ExecuteCommand(action.serviceName, action.command);
|
||||||
|
break;
|
||||||
|
case ServiceManagerAction.Type.SetServiceOption:
|
||||||
|
SetServiceOptionValue(action.serviceName, action.option, action.value);
|
||||||
|
break;
|
||||||
|
case ServiceManagerAction.Type.Start:
|
||||||
|
StartService(action.serviceName);
|
||||||
|
break;
|
||||||
|
case ServiceManagerAction.Type.Stop:
|
||||||
|
StopService(action.serviceName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void View()
|
||||||
|
{
|
||||||
|
GetServiceNames();
|
||||||
|
GetRunningServiceNames();
|
||||||
|
GetModuleNames();
|
||||||
|
GetLogBuffers();
|
||||||
|
GetOptions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using GameServiceWarden.Core.Module;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.Core.Persistence
|
||||||
|
{
|
||||||
|
public interface IPersistent<V> : IReadOnlyPersistent<V>
|
||||||
|
{
|
||||||
|
new V this[string key] { get; set; }
|
||||||
|
|
||||||
|
void AddToPersistence(string key, V value);
|
||||||
|
void Clear();
|
||||||
|
bool Delete(string key);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using GameServiceWarden.ModuleFramework;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.Core.Persistence
|
||||||
|
{
|
||||||
|
public interface IReadOnlyPersistent<V> : IEnumerable<KeyValuePair<string, V>>
|
||||||
|
{
|
||||||
|
V this[string key] { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The directory for this dictionary to use.
|
||||||
|
/// </summary>
|
||||||
|
string MapDirectory { get; }
|
||||||
|
int Count { get; }
|
||||||
|
IEnumerable<V> Values { get; }
|
||||||
|
IEnumerable<string> Keys { get; }
|
||||||
|
|
||||||
|
bool ContainsKey(string key);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The path to the data representing a specific key.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The key to get the path for.</param>
|
||||||
|
/// <returns>A <see cref="string"/> representing the key.</returns>
|
||||||
|
string GetPathForKey(string key);
|
||||||
|
bool TryLoadValue(string key, [MaybeNullWhen(false)] out V value);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,172 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.IO;
|
||||||
|
using GameServiceWarden.Core.Module;
|
||||||
|
using GameServiceWarden.ModuleFramework;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.Core.Persistence
|
||||||
|
{
|
||||||
|
public class ServiceDescriptorPersistence : IPersistent<IReadOnlyDictionary<string, string>>
|
||||||
|
{
|
||||||
|
private readonly string mapDirectory;
|
||||||
|
private const string EXTENSION = ".sin";
|
||||||
|
|
||||||
|
|
||||||
|
public ServiceDescriptorPersistence(string mapDirectory)
|
||||||
|
{
|
||||||
|
this.mapDirectory = mapDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<string, string> this[string key]
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SaveService(key, value[ServiceDescriptor.ASSEMBLY_PROPERTY], value[ServiceDescriptor.MODULE_PROPERTY]);
|
||||||
|
}
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!ContainsKey(key)) throw new KeyNotFoundException();
|
||||||
|
Dictionary<string, string> info = new Dictionary<string, string>();
|
||||||
|
info[ServiceDescriptor.ASSEMBLY_PROPERTY] = GetServiceInfoValue(key, ServiceDescriptor.ASSEMBLY_PROPERTY);
|
||||||
|
info[ServiceDescriptor.MODULE_PROPERTY] = GetServiceInfoValue(key, ServiceDescriptor.MODULE_PROPERTY);
|
||||||
|
return new ReadOnlyDictionary<string, string>(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string MapDirectory { get { return mapDirectory; } }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public int Count
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Directory.GetFiles(mapDirectory).Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<IReadOnlyDictionary<string,string>> Values {
|
||||||
|
get {
|
||||||
|
IEnumerable<string> keys = Keys;
|
||||||
|
List<IReadOnlyDictionary<string,string>> res = new List<IReadOnlyDictionary<string,string>>();
|
||||||
|
foreach (string key in keys)
|
||||||
|
{
|
||||||
|
res.Add(this[key]);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> Keys {
|
||||||
|
get {
|
||||||
|
IEnumerable<string> keys = Directory.EnumerateDirectories(mapDirectory);
|
||||||
|
List<string> res = new List<string>();
|
||||||
|
foreach (string key in keys)
|
||||||
|
{
|
||||||
|
res.Add(Path.GetDirectoryName(key));
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddToPersistence(string key, IReadOnlyDictionary<string,string> value)
|
||||||
|
{
|
||||||
|
if (key == null) throw new ArgumentNullException();
|
||||||
|
if (ContainsKey(key)) throw new ArgumentException();
|
||||||
|
this[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
IEnumerable<string> directories = Keys;
|
||||||
|
foreach (string directory in directories)
|
||||||
|
{
|
||||||
|
Directory.Delete(directory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContainsKey(string key)
|
||||||
|
{
|
||||||
|
return Directory.Exists(GetPathForKey(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<KeyValuePair<string, IReadOnlyDictionary<string,string>>> GetEnumerator()
|
||||||
|
{
|
||||||
|
IEnumerable<string> keys = Keys;
|
||||||
|
List<KeyValuePair<string, IReadOnlyDictionary<string,string>>> result = new List<KeyValuePair<string, IReadOnlyDictionary<string,string>>>();
|
||||||
|
foreach (string key in keys)
|
||||||
|
{
|
||||||
|
result.Add(new KeyValuePair<string, IReadOnlyDictionary<string,string>>(key, this[key]));
|
||||||
|
}
|
||||||
|
return result.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetPathForKey(string key)
|
||||||
|
{
|
||||||
|
return Path.Combine(mapDirectory, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Delete(string key)
|
||||||
|
{
|
||||||
|
if (!ContainsKey(key)) return false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.Delete(GetPathForKey(key));
|
||||||
|
}
|
||||||
|
catch (System.Exception)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryLoadValue(string key, [MaybeNullWhen(false)] out IReadOnlyDictionary<string,string> value)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
value = this[key];
|
||||||
|
} catch (KeyNotFoundException) {
|
||||||
|
value = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveService(string key, string assemblyName, string moduleName)
|
||||||
|
{
|
||||||
|
if (key == null) throw new ArgumentNullException("key");
|
||||||
|
if (assemblyName == null) throw new ArgumentNullException("assemblyName");
|
||||||
|
if (moduleName == null) throw new ArgumentNullException("moduleName");
|
||||||
|
string serviceInfoPath = GetPathForKey(key);
|
||||||
|
Directory.CreateDirectory(serviceInfoPath);
|
||||||
|
using (StreamWriter writer = File.CreateText(Path.Combine(serviceInfoPath, key + EXTENSION)))
|
||||||
|
{
|
||||||
|
writer.WriteLine($"{ServiceDescriptor.ASSEMBLY_PROPERTY}: {assemblyName}");
|
||||||
|
writer.WriteLine($"{ServiceDescriptor.MODULE_PROPERTY}: {moduleName}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetServiceInfoValue(string key, string value)
|
||||||
|
{
|
||||||
|
string path = GetPathForKey(key);
|
||||||
|
IEnumerable<string> lines = File.ReadAllLines(Path.Combine(path, key + EXTENSION));
|
||||||
|
foreach (string line in lines)
|
||||||
|
{
|
||||||
|
if (line.StartsWith($"{value}: "))
|
||||||
|
{
|
||||||
|
return line.Substring(value.Length + 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new FormatException($"\"{path}\" is corrupted. Could not find value for: {value}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,128 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.IO;
|
||||||
|
using GameServiceWarden.Core.Module;
|
||||||
|
using GameServiceWarden.ModuleFramework;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.Core.Persistence
|
||||||
|
{
|
||||||
|
public class ServiceModules : IReadOnlyPersistent<IReadOnlyDictionary<string, IServiceModule>>
|
||||||
|
{
|
||||||
|
private readonly string mapDirectory;
|
||||||
|
|
||||||
|
private readonly ModuleLoader loader = new ModuleLoader();
|
||||||
|
|
||||||
|
public ServiceModules(string mapDirectory)
|
||||||
|
{
|
||||||
|
this.mapDirectory = mapDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<string, IServiceModule> this[string key]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!ContainsKey(key)) throw new KeyNotFoundException($"Key \"{key}\" not found.");
|
||||||
|
Dictionary<string, IServiceModule> res = new Dictionary<string, IServiceModule>();
|
||||||
|
IEnumerable<IServiceModule> modules = loader.LoadModules(GetPathForKey(key));
|
||||||
|
foreach (IServiceModule module in modules)
|
||||||
|
{
|
||||||
|
res.Add(module.Name, module);
|
||||||
|
}
|
||||||
|
return new ReadOnlyDictionary<string, IServiceModule>(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string MapDirectory
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return mapDirectory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> Keys
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
IEnumerable<string> files = Directory.EnumerateFiles(mapDirectory);
|
||||||
|
foreach (string file in files)
|
||||||
|
{
|
||||||
|
if (Path.GetExtension(file).ToLower().Equals("dll"))
|
||||||
|
{
|
||||||
|
yield return Path.GetFileName(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<IReadOnlyDictionary<string, IServiceModule>> Values
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
IEnumerable<string> keys = Keys;
|
||||||
|
foreach (string key in keys)
|
||||||
|
{
|
||||||
|
yield return this[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
IEnumerable<string> files = Directory.EnumerateFiles(mapDirectory);
|
||||||
|
foreach (string file in files)
|
||||||
|
{
|
||||||
|
if (Path.GetExtension(file).ToLower().Equals("dll"))
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContainsKey(string key)
|
||||||
|
{
|
||||||
|
string path = GetPathForKey(key);
|
||||||
|
return File.Exists(path) && Path.GetExtension(path).ToLower().Equals("dll");
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<KeyValuePair<string, IReadOnlyDictionary<string, IServiceModule>>> GetEnumerator()
|
||||||
|
{
|
||||||
|
IEnumerable<string> keys = Keys;
|
||||||
|
foreach (string key in keys)
|
||||||
|
{
|
||||||
|
yield return new KeyValuePair<string, IReadOnlyDictionary<string, IServiceModule>>(key, this[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetPathForKey(string key)
|
||||||
|
{
|
||||||
|
return mapDirectory + Path.DirectorySeparatorChar + key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryLoadValue(string key, [MaybeNullWhen(false)] out IReadOnlyDictionary<string, IServiceModule> value)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
value = this[key];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (KeyNotFoundException)
|
||||||
|
{
|
||||||
|
value = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
GameServiceWarden/GameServiceWarden.Core/Program.cs
Normal file
12
GameServiceWarden/GameServiceWarden.Core/Program.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.Core
|
||||||
|
{
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
GameServiceWarden/GameServiceWarden.Core/UI/IPCController.cs
Normal file
40
GameServiceWarden/GameServiceWarden.Core/UI/IPCController.cs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Text.Json;
|
||||||
|
using GameServiceWarden.InteractionAPI;
|
||||||
|
using GameServiceWarden.InteractionAPI.Communicable.Requests;
|
||||||
|
using GameServiceWarden.Core.Module;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.Core.UI
|
||||||
|
{
|
||||||
|
public class IPCController
|
||||||
|
{
|
||||||
|
private IPCMediator mediator;
|
||||||
|
private IServiceManagerActionExecuter serviceExecutioner;
|
||||||
|
|
||||||
|
public IPCController(IPCMediator mediator, IServiceManagerActionExecuter serviceExecutioner)
|
||||||
|
{
|
||||||
|
this.mediator = mediator;
|
||||||
|
this.serviceExecutioner = serviceExecutioner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Process() {
|
||||||
|
Logger.Log("Beginning to process interface requests.");
|
||||||
|
mediator.Open();
|
||||||
|
(string, CommunicableType, byte[]) action;
|
||||||
|
while (mediator.RequestQueue.TryTake(out action))
|
||||||
|
{
|
||||||
|
switch (action.Item2)
|
||||||
|
{
|
||||||
|
case CommunicableType.Delta:
|
||||||
|
ServiceRequest delta = JsonSerializer.Deserialize<ServiceRequest>(action.Item3);
|
||||||
|
serviceExecutioner.ExecuteAction(delta.serviceManagerAction);
|
||||||
|
break;
|
||||||
|
case CommunicableType.View:
|
||||||
|
serviceExecutioner.View();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mediator.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
283
GameServiceWarden/GameServiceWarden.Core/UI/IPCMediator.cs
Normal file
283
GameServiceWarden/GameServiceWarden.Core/UI/IPCMediator.cs
Normal file
@ -0,0 +1,283 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Pipes;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using GameServiceWarden.InteractionAPI;
|
||||||
|
using GameServiceWarden.InteractionAPI.Communicable.Requests;
|
||||||
|
using GameServiceWarden.InteractionAPI.Communicable.Responses;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.Core.UI
|
||||||
|
{
|
||||||
|
public class IPCMediator
|
||||||
|
{
|
||||||
|
private const int TIMEOUT = 1000;
|
||||||
|
public string PipeName {get { return name + ".pipe"; } }
|
||||||
|
private readonly string name;
|
||||||
|
private readonly ConcurrentDictionary<string, (NamedPipeServerStream, Task)> pipes;
|
||||||
|
public BlockingCollection<(string, CommunicableType, byte[])> RequestQueue { get; private set; }
|
||||||
|
private volatile bool active;
|
||||||
|
public bool IsRunning { get => active; }
|
||||||
|
private Task connectionTask;
|
||||||
|
private CancellationTokenSource stopAcceptingToken;
|
||||||
|
private volatile NamedPipeServerStream connectingPipe;
|
||||||
|
|
||||||
|
public IPCMediator(string name)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
pipes = new ConcurrentDictionary<string, (NamedPipeServerStream, Task)>();
|
||||||
|
RequestQueue = new BlockingCollection<(string, CommunicableType, byte[])>(new ConcurrentQueue<(string, CommunicableType, byte[])>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Open()
|
||||||
|
{
|
||||||
|
if (active) throw new InvalidOperationException("IPC already opened.");
|
||||||
|
Logger.Log($"IPCMediator with name \"{name}\" opened.");
|
||||||
|
active = true;
|
||||||
|
stopAcceptingToken = new CancellationTokenSource();
|
||||||
|
connectionTask = AcceptConnections();
|
||||||
|
Logger.Log($"IPCMediator \"{name}\" has begun asynchronously accepting interfaces.", LogLevel.Debug);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
if (!active) throw new InvalidOperationException("IPC not open.");
|
||||||
|
Logger.Log("Closing IPC mediator.");
|
||||||
|
active = false;
|
||||||
|
stopAcceptingToken.Cancel();
|
||||||
|
connectingPipe.Dispose();
|
||||||
|
InitiateDisconnectAll("Closing GameServiceWarden.").Wait();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!connectionTask.Wait(TIMEOUT)) throw new TimeoutException($"IPCMediator \"{name}\" was unable to stop accepting task within {TIMEOUT}ms.");
|
||||||
|
}
|
||||||
|
catch (AggregateException e)
|
||||||
|
{
|
||||||
|
e.Handle((exception) => exception is TaskCanceledException || (exception is SocketException && exception.Message.Equals("Operation canceled")));
|
||||||
|
}
|
||||||
|
RequestQueue.CompleteAdding();
|
||||||
|
stopAcceptingToken.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ReplyAll(CommunicableType type, byte[] data)
|
||||||
|
{
|
||||||
|
IEnumerable<string> identifiers = pipes.Keys;
|
||||||
|
Stack<Task> replyTasks = new Stack<Task>();
|
||||||
|
foreach (string identifier in identifiers)
|
||||||
|
{
|
||||||
|
replyTasks.Push(Reply(identifier, type, data));
|
||||||
|
}
|
||||||
|
Task replyTask;
|
||||||
|
while (replyTasks.TryPop(out replyTask))
|
||||||
|
{
|
||||||
|
await replyTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Reply(string identifier, CommunicableType type, byte[] data)
|
||||||
|
{
|
||||||
|
CancellationTokenSource cancel = new CancellationTokenSource(1000);
|
||||||
|
byte[] header = ResponseHeader.Encode(type, (uint)data.Length);
|
||||||
|
await pipes[identifier].Item1.WriteAsync(header, 0, header.Length, cancel.Token);
|
||||||
|
await pipes[identifier].Item1.WriteAsync(data, cancel.Token);
|
||||||
|
cancel.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitiateDisconnect(string identifier, string reason)
|
||||||
|
{
|
||||||
|
Logger.Log($"Disconnecting \"{identifier}\". Reason: \"{reason}\"");
|
||||||
|
DisconnectResponse response;
|
||||||
|
response.reason = reason;
|
||||||
|
await Reply(identifier, CommunicableType.Disconnect, JsonSerializer.SerializeToUtf8Bytes(response));
|
||||||
|
(NamedPipeServerStream, Task) pipeAndTask = pipes[identifier];
|
||||||
|
pipeAndTask.Item1.Close();
|
||||||
|
await pipeAndTask.Item2;
|
||||||
|
Logger.Log($"Successfully disconnected \"{identifier}\".");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitiateDisconnectAll(string reason) {
|
||||||
|
Logger.Log($"Disconnecting all of {pipes.Count} interfaces.");
|
||||||
|
foreach (string id in pipes.Keys)
|
||||||
|
{
|
||||||
|
await InitiateDisconnect(id, reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AcceptConnections()
|
||||||
|
{
|
||||||
|
List<Task> connectionTasks = new List<Task>();
|
||||||
|
Logger.Log("Accepting Interface connections.");
|
||||||
|
while (active)
|
||||||
|
{
|
||||||
|
connectingPipe = new NamedPipeServerStream(PipeName, PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
|
||||||
|
Logger.Log("Waiting for connection.", LogLevel.Debug);
|
||||||
|
await connectingPipe.WaitForConnectionAsync(stopAcceptingToken.Token);
|
||||||
|
connectionTasks.Add(OnConnection(connectingPipe));
|
||||||
|
for (int i = 0; i < connectionTasks.Count; i++)
|
||||||
|
{
|
||||||
|
if (connectionTasks[i].IsCompleted) {
|
||||||
|
Task connectionTask = connectionTasks[i];
|
||||||
|
connectionTasks.RemoveAt(i);
|
||||||
|
connectionTask.Wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Logger.Log("Waiting for connection tasks.", LogLevel.Debug);
|
||||||
|
foreach (Task task in connectionTasks)
|
||||||
|
{
|
||||||
|
task.Wait();
|
||||||
|
}
|
||||||
|
Logger.Log("Stopped accepting pipe connections.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnConnection(NamedPipeServerStream pipe)
|
||||||
|
{
|
||||||
|
Logger.Log("Interface attempting to connect.", LogLevel.Debug);
|
||||||
|
byte[] headerBuffer = new byte[sizeof(uint) * 2];
|
||||||
|
int headerFill = 0;
|
||||||
|
CancellationTokenSource headerCancel = new CancellationTokenSource(TIMEOUT);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int readLength;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
readLength = await pipe.ReadAsync(headerBuffer, headerFill, headerBuffer.Length - headerFill, headerCancel.Token);
|
||||||
|
headerFill += readLength;
|
||||||
|
} while (readLength != 0 && headerFill != headerBuffer.Length);
|
||||||
|
}
|
||||||
|
catch (AggregateException e)
|
||||||
|
{
|
||||||
|
e.Handle((exception) => exception is TaskCanceledException);
|
||||||
|
Logger.Log($"Interface did not send header data within {TIMEOUT}ms.", LogLevel.Debug);
|
||||||
|
} finally {
|
||||||
|
await pipe.DisposeAsync();
|
||||||
|
headerCancel.Dispose();
|
||||||
|
}
|
||||||
|
if (headerFill != headerBuffer.Length) {
|
||||||
|
Logger.Log($"Interface failed to send header data.", LogLevel.Debug);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CommunicableType comType;
|
||||||
|
uint bodyLength;
|
||||||
|
RequestHeader.Decode(headerBuffer, out comType, out bodyLength); //TODO do exception check.
|
||||||
|
|
||||||
|
byte[] bodyBuffer = new byte[bodyLength];
|
||||||
|
int bodyFill = 0;
|
||||||
|
CancellationTokenSource bodyCancel = new CancellationTokenSource(TIMEOUT);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int readLength = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
readLength = await pipe.ReadAsync(bodyBuffer, bodyFill, bodyBuffer.Length - bodyFill, bodyCancel.Token);
|
||||||
|
bodyFill += readLength;
|
||||||
|
} while (readLength != 0 && bodyFill != headerBuffer.Length);
|
||||||
|
}
|
||||||
|
catch (AggregateException e)
|
||||||
|
{
|
||||||
|
e.Handle((exception) => exception is TaskCanceledException);
|
||||||
|
Logger.Log($"Interface failed to send body data within {TIMEOUT}.", LogLevel.Debug);
|
||||||
|
} finally {
|
||||||
|
await pipe.DisposeAsync();
|
||||||
|
bodyCancel.Dispose();
|
||||||
|
}
|
||||||
|
if (bodyFill != bodyBuffer.Length) {
|
||||||
|
Logger.Log($"Interface failed to send body data.", LogLevel.Debug);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnectRequest request = JsonSerializer.Deserialize<ConnectRequest>(bodyBuffer);
|
||||||
|
ConnectResponse response = new ConnectResponse();
|
||||||
|
bool requestAccepted = false;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(request.requestedIdentifier)) {
|
||||||
|
response.invalidName = true;
|
||||||
|
response.errorMsg = $"The requested identifier \"{request.requestedIdentifier}\" is null or whitespace.";
|
||||||
|
Logger.Log(response.errorMsg, LogLevel.Debug);
|
||||||
|
} else if (pipes.ContainsKey(request.requestedIdentifier)) {
|
||||||
|
response.invalidName = true;
|
||||||
|
response.nameTaken = true;
|
||||||
|
response.errorMsg = $"Interface requested identifier \"{request.requestedIdentifier}\" is taken.";
|
||||||
|
Logger.Log(response.errorMsg, LogLevel.Debug);
|
||||||
|
} else {
|
||||||
|
requestAccepted = true;
|
||||||
|
response.identifier = request.requestedIdentifier;
|
||||||
|
}
|
||||||
|
CancellationTokenSource cancelResponse = new CancellationTokenSource(TIMEOUT);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await pipe.WriteAsync(JsonSerializer.SerializeToUtf8Bytes(response), cancelResponse.Token);
|
||||||
|
}
|
||||||
|
catch (AggregateException e)
|
||||||
|
{
|
||||||
|
e.Handle((exception) => exception is TaskCanceledException);
|
||||||
|
Logger.Log($"Interface did not receive response within {TIMEOUT}ms.", LogLevel.Debug);
|
||||||
|
}
|
||||||
|
if (!requestAccepted) {
|
||||||
|
cancelResponse.Dispose();
|
||||||
|
await pipe.DisposeAsync();
|
||||||
|
Logger.Log($"Interface failed to connect.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Log($"Interface \"{response.identifier}\" connected.");
|
||||||
|
pipes[request.requestedIdentifier] = (pipe, Listen(response.identifier, pipe));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Listen(string identifier, NamedPipeServerStream pipe)
|
||||||
|
{
|
||||||
|
Logger.Log($"Started listening to interface \"{identifier}\".", LogLevel.Debug);
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
byte[] headerBuffer = new byte[sizeof(uint) * 2];
|
||||||
|
byte[] bodyBuffer = null;
|
||||||
|
|
||||||
|
int bodyFill = 0;
|
||||||
|
int headerFill = 0;
|
||||||
|
|
||||||
|
int readLength = 0;
|
||||||
|
|
||||||
|
CommunicableType? comType = null;
|
||||||
|
while ((readLength = await pipe.ReadAsync(buffer, 0, buffer.Length)) > 0)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < readLength; i++)
|
||||||
|
{
|
||||||
|
if (bodyBuffer == null)
|
||||||
|
{
|
||||||
|
headerBuffer[headerFill] = buffer[i];
|
||||||
|
headerFill++;
|
||||||
|
if (headerFill == headerBuffer.Length)
|
||||||
|
{
|
||||||
|
uint length;
|
||||||
|
CommunicableType type;
|
||||||
|
RequestHeader.Decode(headerBuffer, out type, out length);
|
||||||
|
bodyBuffer = new byte[length];
|
||||||
|
headerFill = 0;
|
||||||
|
comType = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bodyBuffer[bodyFill] = buffer[i];
|
||||||
|
bodyFill++;
|
||||||
|
if (bodyFill == bodyBuffer.Length)
|
||||||
|
{
|
||||||
|
RequestQueue.Add((identifier, comType.Value, bodyBuffer));
|
||||||
|
bodyFill = 0;
|
||||||
|
bodyBuffer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Logger.Log($"Pipe for interface \"{identifier}\" has closed.", LogLevel.Debug);
|
||||||
|
(NamedPipeServerStream, Task) removedPipe;
|
||||||
|
pipes.TryRemove(identifier, out removedPipe);
|
||||||
|
await removedPipe.Item1.DisposeAsync();
|
||||||
|
Logger.Log($"Stopped listening to interface \"{identifier}\".", LogLevel.Debug);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
GameServiceWarden/GameServiceWarden.Core/UI/IPCPresenter.cs
Normal file
30
GameServiceWarden/GameServiceWarden.Core/UI/IPCPresenter.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using GameServiceWarden.InteractionAPI;
|
||||||
|
using GameServiceWarden.InteractionAPI.Module;
|
||||||
|
using GameServiceWarden.Core.Module;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.Core.UI
|
||||||
|
{
|
||||||
|
public class IPCPresenter : IServiceManagerMonitor
|
||||||
|
{
|
||||||
|
private IPCMediator mediator;
|
||||||
|
|
||||||
|
public IPCPresenter(IPCMediator mediator)
|
||||||
|
{
|
||||||
|
this.mediator = mediator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Present(ServiceManagerTotal state)
|
||||||
|
{
|
||||||
|
Task replyTask = mediator.ReplyAll(CommunicableType.View, JsonSerializer.SerializeToUtf8Bytes(state));
|
||||||
|
replyTask.Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Present(ServiceManagerDelta delta)
|
||||||
|
{
|
||||||
|
Task replyTask = mediator.ReplyAll(CommunicableType.Delta, JsonSerializer.SerializeToUtf8Bytes(delta));
|
||||||
|
replyTask.Wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
385
GameServiceWarden/GameServiceWarden.Core/UMLSketch.drawio
Normal file
385
GameServiceWarden/GameServiceWarden.Core/UMLSketch.drawio
Normal file
@ -0,0 +1,385 @@
|
|||||||
|
<mxfile host="65bd71144e" pages="2">
|
||||||
|
<diagram id="LHR7ubqCPd17_LyHkaH9" name="Structure">
|
||||||
|
<mxGraphModel dx="1785" dy="689" 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="dmd0HlDYcxYugIlahWj0-5" value="ServiceDescriptor" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="695" y="1100" width="510" height="420" as="geometry">
|
||||||
|
<mxRectangle x="762" y="1030" width="130" height="26" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="dmd0HlDYcxYugIlahWj0-6" value="+ ServiceName: string property - serviceName: string - runningUID: Guid - running: bool - service: IService - ServiceLogPipeName: string property - moduleName: string - assemblyName: string - logStreamListeners: ConcurrentStack<NamedPipeServerStream> - logUpdateTask: Task - acceptingTask: Task - stopToken: CancellationTokenSource - configurables: IReadOnlyDictionary<string, IServiceConfigurable> + ServiceStateChangeEvent: event EventHandler<bool>" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" parent="dmd0HlDYcxYugIlahWj0-5" vertex="1">
|
||||||
|
<mxGeometry y="26" width="510" height="204" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="dmd0HlDYcxYugIlahWj0-7" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;" parent="dmd0HlDYcxYugIlahWj0-5" vertex="1">
|
||||||
|
<mxGeometry y="230" width="510" height="8" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="dmd0HlDYcxYugIlahWj0-8" value="+ Start(): void + Stop(): void + ExecuteCommand(command: string): void + GetConfigurableOptions(): ISet<string> + SetConfigurableValue(configurationName: string, value: string): bool + GetConfigurableValue(configurationName: string): string + GetServiceState(): bool + GetModuleName(): string + GetassemblyName(): string - OnServiceStateChange(sender: object, running: bool): void - AcceptLogConnections(): Task - BroadcastLog(): Task" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" parent="dmd0HlDYcxYugIlahWj0-5" vertex="1">
|
||||||
|
<mxGeometry y="238" width="510" height="182" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="dmd0HlDYcxYugIlahWj0-15" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;dashed=1;endArrow=open;endFill=0;" parent="1" source="dmd0HlDYcxYugIlahWj0-11" target="dmd0HlDYcxYugIlahWj0-5" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="wwlaSBDwwZOn0hO83bWU-6" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;endArrow=block;endFill=0;" parent="1" source="dmd0HlDYcxYugIlahWj0-11" target="wwlaSBDwwZOn0hO83bWU-2" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="fdKXkHfjRXYybK0fejAG-2" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;endArrow=open;endFill=0;" parent="1" source="dmd0HlDYcxYugIlahWj0-11" target="zFFzFwxISwJASp9ezwbr-1" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="SI3d9EEbteElKQB4Ic5T-1" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;endArrow=open;endFill=0;" parent="1" source="dmd0HlDYcxYugIlahWj0-11" target="fdKXkHfjRXYybK0fejAG-9" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="29" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;endArrow=open;endFill=0;" parent="1" source="dmd0HlDYcxYugIlahWj0-11" target="24" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="39" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;endArrow=open;endFill=0;" parent="1" source="dmd0HlDYcxYugIlahWj0-11" target="35" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="dmd0HlDYcxYugIlahWj0-11" value="ServiceManager" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="669.5" y="640" width="561" height="380" as="geometry">
|
||||||
|
<mxRectangle x="25" y="490" width="120" height="26" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="dmd0HlDYcxYugIlahWj0-12" value="- managerMonitor: IServiceManagerMonitor - running: ConcurrentDictionary<string, ServiceDescriptor> - services: IPersistent<IReadOnlyDictionary<string, string>> - modules: IReadOnlyPersistent<IReadOnlyDictionary<string, IServiceModule>> - descriptorCache: LRUCache<string, ServiceDescriptor>" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" parent="dmd0HlDYcxYugIlahWj0-11" vertex="1">
|
||||||
|
<mxGeometry y="26" width="561" height="84" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="dmd0HlDYcxYugIlahWj0-13" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;" parent="dmd0HlDYcxYugIlahWj0-11" vertex="1">
|
||||||
|
<mxGeometry y="110" width="561" height="8" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="dmd0HlDYcxYugIlahWj0-14" value="+ CreateService(serviceName: string, assemblyName: string, moduleName: string): void + DeleteService(serviceName: string): void + GetModuleNames(): IEnumerable<string> + GetServiceNames(): IEnumerable<string> + GetRunningServiceNames(): IEnumerable<string> - GetServiceOptions(serviceName: string): IEnumerable<string> - GetServiceOptionValue(serviceName: string, optionName: string): IEnumerable<string> + GetOptions(): IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> + SetServiceOptionValue(serviceName: string, optionName: string, string: value): bool + StartService(serviceName: string): void + StopService(serviceName: string): void + ExecuteCommand(serviceName: string, command: string): void - GetServiceLogBuffer(serviceName: string): byte[] + GetLogBuffers(): IReadOnlyDictionary<string, byte[]> - GenerateDescriptor(name: string, assembly: string, module: string): ServiceDescriptor - OnServiceStateChange(sender: object, state: bool): void - OnLogUpdated(sender: object, update: string)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" parent="dmd0HlDYcxYugIlahWj0-11" vertex="1">
|
||||||
|
<mxGeometry y="118" width="561" height="262" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="wwlaSBDwwZOn0hO83bWU-9" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;endArrow=block;endFill=0;" parent="1" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<mxPoint x="1679.5" y="202" as="sourcePoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="K1k0_LUP-qlT_3mlrptx-1" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;endArrow=open;endFill=0;" parent="1" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<mxPoint x="1414.5" y="133" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="30" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;endArrow=open;endFill=0;" parent="1" source="wwlaSBDwwZOn0hO83bWU-2" target="24" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="wwlaSBDwwZOn0hO83bWU-2" value="<<Interface>> IServiceExecuter" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=40;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1386" y="406" width="305" height="90" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="wwlaSBDwwZOn0hO83bWU-4" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;" parent="wwlaSBDwwZOn0hO83bWU-2" vertex="1">
|
||||||
|
<mxGeometry y="40" width="305" height="8" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="wwlaSBDwwZOn0hO83bWU-5" value="+ ExecuteAction(action: ServiceManagerAction): void + View(): void" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" parent="wwlaSBDwwZOn0hO83bWU-2" vertex="1">
|
||||||
|
<mxGeometry y="48" width="305" height="42" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="V3nv0dmUtDNsDw_gxP-z-2" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;endArrow=open;endFill=0;" parent="1" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<mxPoint x="850" y="78" as="sourcePoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="fdKXkHfjRXYybK0fejAG-3" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;dashed=1;endArrow=open;endFill=0;" parent="1" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<mxPoint x="340" y="192" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="fdKXkHfjRXYybK0fejAG-4" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;endArrow=block;endFill=0;" parent="1" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<mxPoint x="340" y="192" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="fdKXkHfjRXYybK0fejAG-1" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;endArrow=block;endFill=0;" parent="1" source="15" target="zFFzFwxISwJASp9ezwbr-1" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<mxPoint x="495" y="378" as="sourcePoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="SI3d9EEbteElKQB4Ic5T-5" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;endArrow=open;endFill=0;" parent="1" source="zFFzFwxISwJASp9ezwbr-1" target="fdKXkHfjRXYybK0fejAG-9" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<mxPoint x="270" y="610" as="sourcePoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="5" value="Use" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="SI3d9EEbteElKQB4Ic5T-5" vertex="1" connectable="0">
|
||||||
|
<mxGeometry x="0.1309" relative="1" as="geometry">
|
||||||
|
<mxPoint as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="41" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;endArrow=open;endFill=0;" parent="1" source="zFFzFwxISwJASp9ezwbr-1" target="35" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="zFFzFwxISwJASp9ezwbr-1" value="<<Interface>> IServiceManagerMonitor" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=40;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="278" y="460" width="295" height="90" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="zFFzFwxISwJASp9ezwbr-3" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;" parent="zFFzFwxISwJASp9ezwbr-1" vertex="1">
|
||||||
|
<mxGeometry y="40" width="295" height="8" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="zFFzFwxISwJASp9ezwbr-4" value="+ Present(state: ServiceManagerState): void + Present(delta: ServiceManagerDelta): void" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" parent="zFFzFwxISwJASp9ezwbr-1" vertex="1">
|
||||||
|
<mxGeometry y="48" width="295" height="42" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="fdKXkHfjRXYybK0fejAG-9" value="<<DS>> ServiceManagerTotal" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=40;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="20" y="850" width="485" height="138" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="fdKXkHfjRXYybK0fejAG-10" value="+ services: ICollection<string> + running: ICollection<string> + modules: ICollection<string> + logs: IReadOnlyDictionary<string, string> + serviceOptions: IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>>" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" parent="fdKXkHfjRXYybK0fejAG-9" vertex="1">
|
||||||
|
<mxGeometry y="40" width="485" height="90" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="fdKXkHfjRXYybK0fejAG-11" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;" parent="fdKXkHfjRXYybK0fejAG-9" vertex="1">
|
||||||
|
<mxGeometry y="130" width="485" height="8" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="SI3d9EEbteElKQB4Ic5T-10" value="<<Enum>> ServiceManagerAction.Type" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=40;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1336" y="610" width="180" height="168" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="SI3d9EEbteElKQB4Ic5T-11" value="+ Start + Stop + CreateService + DeleteService + ExecuteCommand + SetServiceOption" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" parent="SI3d9EEbteElKQB4Ic5T-10" vertex="1">
|
||||||
|
<mxGeometry y="40" width="180" height="120" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="SI3d9EEbteElKQB4Ic5T-12" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;" parent="SI3d9EEbteElKQB4Ic5T-10" vertex="1">
|
||||||
|
<mxGeometry y="160" width="180" height="8" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="6" value="IPCMediator" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="685" y="80" width="520" height="350" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="7" value="- CONNECT_TIMEOUT: int + PipeName: property string property (appends ".pipe") - name: string - pipes: ConcurrentDictionary<string, (NamedPipeServerStream, Task)> + RequestQueue: default property of BlockingCollection<(string, CommunicableType, Byte[])> - active: bool + IsRunning: bool property - connectionTask: Task - stopAcceptingToken: CancellationTokenSource - connectingPipe: NamedPipeServerStream" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" parent="6" vertex="1">
|
||||||
|
<mxGeometry y="26" width="520" height="154" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="8" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;" parent="6" vertex="1">
|
||||||
|
<mxGeometry y="180" width="520" height="8" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="9" value="+ Open(): void + Close(): void + ReplyAll(type: CommunicableType, data: byte[]): Task + Reply(identifier: string, type: CommunicableType, data: byte[]): Task + InitiateDisconnect(identifier: string, reason: string): Task + InitiateDisconnectAll(reason: string): Task - AcceptConnections(): Task - OnConnection(pipe: NamedPipeServerStream): Task - Listen(identifier: string, pipe: NamedPipeServerStream): Task" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" parent="6" vertex="1">
|
||||||
|
<mxGeometry y="188" width="520" height="162" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="20" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;endArrow=open;endFill=0;" parent="1" source="10" target="6" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="21" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;endArrow=open;endFill=0;" parent="1" source="10" target="wwlaSBDwwZOn0hO83bWU-2" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="10" value="IPCController" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1378.5" y="230" width="320" height="104" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="11" value="- mediator: IPCMediator - serviceExecuter: IServiceManagerActionExecuter" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" parent="10" vertex="1">
|
||||||
|
<mxGeometry y="26" width="320" height="44" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="12" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;" parent="10" vertex="1">
|
||||||
|
<mxGeometry y="70" width="320" height="8" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="13" value="+ Process(): void" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" parent="10" vertex="1">
|
||||||
|
<mxGeometry y="78" width="320" height="26" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="19" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;endArrow=open;endFill=0;dashed=1;" parent="1" source="15" target="6" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="15" value="IPCPresenter" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="300" y="252" width="250" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="16" value="- mediator: IPCMediator" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" parent="15" vertex="1">
|
||||||
|
<mxGeometry y="26" width="250" height="26" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="17" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;" parent="15" vertex="1">
|
||||||
|
<mxGeometry y="52" width="250" height="8" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="28" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;endArrow=open;endFill=0;" parent="1" source="24" target="SI3d9EEbteElKQB4Ic5T-10" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="24" value="<<DS>> ServiceManagerAction" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=40;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1096" y="460" width="220" height="132" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="25" value="+ assemblyName: string + moduleName: string + serviceName: string + module: string + action: ServiceManagerAction.Type" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" parent="24" vertex="1">
|
||||||
|
<mxGeometry y="40" width="220" height="84" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="26" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;" parent="24" vertex="1">
|
||||||
|
<mxGeometry y="124" width="220" height="8" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="35" value="<<DS>> ServiceManagerDelta" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=40;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="440" y="630" width="160" height="158" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="36" value="+ substract: bool + service: string + running: string + modules: string + logs: byte[] + optionName: string + optionValue: string" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" parent="35" vertex="1">
|
||||||
|
<mxGeometry y="40" width="160" height="110" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="37" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;" parent="35" vertex="1">
|
||||||
|
<mxGeometry y="150" width="160" height="8" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="Zyebldgwuc7dZQORIN4r-41" value="Text" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="820" y="360" width="60" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="Zyebldgwuc7dZQORIN4r-42" value="This is a UML class diagram according to the "Design Pattern" book as described on page 8." style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="30" y="60" width="220" height="70" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
<diagram id="gj0qHRc3eh050ABAey3g" name="Data-Flow">
|
||||||
|
<mxGraphModel dx="1684" dy="1789" 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="jVG6p58vlRYGO9X4wXeX-0"/>
|
||||||
|
<mxCell id="jVG6p58vlRYGO9X4wXeX-1" parent="jVG6p58vlRYGO9X4wXeX-0"/>
|
||||||
|
<mxCell id="28FAlPysTx9DMYvLwa-2-21" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.3333333333333333;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;endArrow=open;endFill=0;fillColor=#1ba1e2;strokeColor=#006EAF;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-2" target="jVG6p58vlRYGO9X4wXeX-3" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="jVG6p58vlRYGO9X4wXeX-2" value="Actor" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
|
||||||
|
<mxGeometry x="10" y="300" width="30" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="jVG6p58vlRYGO9X4wXeX-12" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;endArrow=open;endFill=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;fillColor=#1ba1e2;strokeColor=#006EAF;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-3" target="jVG6p58vlRYGO9X4wXeX-4" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="28FAlPysTx9DMYvLwa-2-22" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.25;exitDx=0;exitDy=0;entryX=0.75;entryY=0.1;entryDx=0;entryDy=0;entryPerimeter=0;endArrow=open;endFill=0;fillColor=#1ba1e2;strokeColor=#006EAF;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-3" target="jVG6p58vlRYGO9X4wXeX-2" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="345FJoVc2gbAayMsQlD7-0" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.25;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;dashed=1;endArrow=open;endFill=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-3" target="jVG6p58vlRYGO9X4wXeX-4" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="345FJoVc2gbAayMsQlD7-6" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.25;exitY=0;exitDx=0;exitDy=0;entryX=0;entryY=0.25;entryDx=0;entryDy=0;dashed=1;endArrow=open;endFill=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-3" target="28FAlPysTx9DMYvLwa-2-7" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="jVG6p58vlRYGO9X4wXeX-3" value="Console View" style="whiteSpace=wrap;html=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
|
||||||
|
<mxGeometry x="80" y="300" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="jVG6p58vlRYGO9X4wXeX-13" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;endArrow=open;endFill=0;fillColor=#1ba1e2;strokeColor=#006EAF;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-4" target="jVG6p58vlRYGO9X4wXeX-5" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="345FJoVc2gbAayMsQlD7-2" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.75;exitDx=0;exitDy=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;dashed=1;endArrow=open;endFill=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-4" target="jVG6p58vlRYGO9X4wXeX-5" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="jVG6p58vlRYGO9X4wXeX-4" value="string command (request)" style="whiteSpace=wrap;html=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
|
||||||
|
<mxGeometry x="257.5" y="542.5" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="jVG6p58vlRYGO9X4wXeX-8" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;dashed=1;endArrow=block;endFill=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-5" target="jVG6p58vlRYGO9X4wXeX-7" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="28FAlPysTx9DMYvLwa-2-3" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.75;exitY=0;exitDx=0;exitDy=0;entryX=0.75;entryY=1;entryDx=0;entryDy=0;endArrow=open;endFill=0;dashed=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-5" target="jVG6p58vlRYGO9X4wXeX-7" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="UY-EM7-1ECCvWtENr50b-18" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;endArrow=open;endFill=0;strokeColor=#006EAF;fillColor=#1ba1e2;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-5" target="jVG6p58vlRYGO9X4wXeX-9" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="jVG6p58vlRYGO9X4wXeX-5" value="MainController" style="whiteSpace=wrap;html=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
|
||||||
|
<mxGeometry x="417.5" y="542.5" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="jVG6p58vlRYGO9X4wXeX-6" value="http://www.plainionist.net/Implementing-Clean-Architecture-Controller-Presenter/" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
|
||||||
|
<mxGeometry y="840" width="480" height="20" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="jVG6p58vlRYGO9X4wXeX-7" value="&lt;&lt;Interface&gt;&gt;<br>ICommand" style="whiteSpace=wrap;html=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
|
||||||
|
<mxGeometry x="417.5" y="432.5" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="jVG6p58vlRYGO9X4wXeX-10" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;dashed=1;endArrow=block;endFill=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-9" target="jVG6p58vlRYGO9X4wXeX-7" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="UY-EM7-1ECCvWtENr50b-8" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.25;entryDx=0;entryDy=0;endArrow=open;endFill=0;strokeColor=#006EAF;fillColor=#1ba1e2;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-9" target="UY-EM7-1ECCvWtENr50b-1" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<mxPoint x="809.9999999999998" y="512.5000000000002" as="sourcePoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="345FJoVc2gbAayMsQlD7-3" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.25;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;dashed=1;endArrow=open;endFill=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-9" target="UY-EM7-1ECCvWtENr50b-2" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="730" y="558"/>
|
||||||
|
<mxPoint x="730" y="463"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="345FJoVc2gbAayMsQlD7-4" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.75;exitDx=0;exitDy=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;dashed=1;endArrow=open;endFill=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-9" target="UY-EM7-1ECCvWtENr50b-1" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="jVG6p58vlRYGO9X4wXeX-9" value="ServiceController" style="whiteSpace=wrap;html=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
|
||||||
|
<mxGeometry x="572.5" y="542.5" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="UY-EM7-1ECCvWtENr50b-4" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=none;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-1" target="UY-EM7-1ECCvWtENr50b-2" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="UY-EM7-1ECCvWtENr50b-5" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;dashed=1;endArrow=block;endFill=0;strokeColor=#f0f0f0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-1" target="UY-EM7-1ECCvWtENr50b-2" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="UY-EM7-1ECCvWtENr50b-7" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1.008;entryY=0.625;entryDx=0;entryDy=0;entryPerimeter=0;endArrow=open;endFill=0;strokeColor=#f0f0f0;dashed=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-1" target="UY-EM7-1ECCvWtENr50b-1" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="960" y="338"/>
|
||||||
|
<mxPoint x="960" y="580"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="UY-EM7-1ECCvWtENr50b-16" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;endArrow=open;endFill=0;dashed=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-1" target="UY-EM7-1ECCvWtENr50b-11" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="345FJoVc2gbAayMsQlD7-7" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.25;entryDx=0;entryDy=0;dashed=1;endArrow=open;endFill=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-1" target="UY-EM7-1ECCvWtENr50b-10" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="960" y="338"/>
|
||||||
|
<mxPoint x="960" y="83"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="28FAlPysTx9DMYvLwa-2-1" value="ServiceManager" style="whiteSpace=wrap;html=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
|
||||||
|
<mxGeometry x="800" y="307.5" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="28FAlPysTx9DMYvLwa-2-9" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;endArrow=open;endFill=0;fillColor=#1ba1e2;strokeColor=#006EAF;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-5" target="tM_Gde3HH8YiZ2frBV5J-0" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="UY-EM7-1ECCvWtENr50b-12" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;endArrow=block;endFill=0;strokeColor=#f0f0f0;dashed=1;exitX=1;exitY=0.75;exitDx=0;exitDy=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-5" target="UY-EM7-1ECCvWtENr50b-11" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="UY-EM7-1ECCvWtENr50b-13" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.75;entryDx=0;entryDy=0;endArrow=open;endFill=0;strokeColor=#f0f0f0;dashed=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-5" target="UY-EM7-1ECCvWtENr50b-10" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="tM_Gde3HH8YiZ2frBV5J-3" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;dashed=1;endArrow=block;endFill=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-5" target="tM_Gde3HH8YiZ2frBV5J-1" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="28FAlPysTx9DMYvLwa-2-5" value="ServicePresenter" style="whiteSpace=wrap;html=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
|
||||||
|
<mxGeometry x="575" y="82.5" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="28FAlPysTx9DMYvLwa-2-8" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;endArrow=open;endFill=0;fillColor=#1ba1e2;strokeColor=#006EAF;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-7" target="jVG6p58vlRYGO9X4wXeX-3" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="tM_Gde3HH8YiZ2frBV5J-5" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;dashed=1;endArrow=open;endFill=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-7" target="tM_Gde3HH8YiZ2frBV5J-0" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="28FAlPysTx9DMYvLwa-2-7" value="String Output" style="whiteSpace=wrap;html=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
|
||||||
|
<mxGeometry x="260" y="82.5" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="UY-EM7-1ECCvWtENr50b-1" value="ServiceAction &lt;DS&gt;" style="whiteSpace=wrap;html=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
|
||||||
|
<mxGeometry x="800" y="542.5" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="UY-EM7-1ECCvWtENr50b-6" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;endArrow=open;endFill=0;strokeColor=#f0f0f0;dashed=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="UY-EM7-1ECCvWtENr50b-2" target="UY-EM7-1ECCvWtENr50b-1" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="UY-EM7-1ECCvWtENr50b-20" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.25;exitDx=0;exitDy=0;entryX=1;entryY=0.75;entryDx=0;entryDy=0;endArrow=open;endFill=0;strokeColor=#006EAF;fillColor=#1ba1e2;" parent="jVG6p58vlRYGO9X4wXeX-1" source="UY-EM7-1ECCvWtENr50b-1" target="28FAlPysTx9DMYvLwa-2-1" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="UY-EM7-1ECCvWtENr50b-2" value="&lt;&lt;Interface&gt;&gt;<br>IServiceManipulator" style="whiteSpace=wrap;html=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
|
||||||
|
<mxGeometry x="800" y="432.5" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="UY-EM7-1ECCvWtENr50b-22" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.25;entryDx=0;entryDy=0;endArrow=open;endFill=0;strokeColor=#006EAF;fillColor=#1ba1e2;" parent="jVG6p58vlRYGO9X4wXeX-1" source="UY-EM7-1ECCvWtENr50b-10" target="28FAlPysTx9DMYvLwa-2-5" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="UY-EM7-1ECCvWtENr50b-10" value="ServicesResult &lt;DS&gt;" style="whiteSpace=wrap;html=1;fillColor=none;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
|
||||||
|
<mxGeometry x="800" y="67.5" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="UY-EM7-1ECCvWtENr50b-14" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;endArrow=open;endFill=0;strokeColor=#f0f0f0;dashed=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="UY-EM7-1ECCvWtENr50b-11" target="UY-EM7-1ECCvWtENr50b-10" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="UY-EM7-1ECCvWtENr50b-21" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.25;exitDx=0;exitDy=0;entryX=1;entryY=0.75;entryDx=0;entryDy=0;endArrow=open;endFill=0;strokeColor=#006EAF;fillColor=#1ba1e2;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-1" target="UY-EM7-1ECCvWtENr50b-10" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="UY-EM7-1ECCvWtENr50b-11" value="&lt;&lt;Interface&gt;&gt;<br>IServicesMonitor" style="whiteSpace=wrap;html=1;fillColor=none;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
|
||||||
|
<mxGeometry x="800" y="167.5" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="UY-EM7-1ECCvWtENr50b-70" value="" style="line;strokeWidth=2;direction=south;html=1;fillColor=none;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
|
||||||
|
<mxGeometry x="220" y="50" width="10" height="570" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="UY-EM7-1ECCvWtENr50b-71" value="" style="line;strokeWidth=2;direction=south;html=1;fillColor=none;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
|
||||||
|
<mxGeometry x="760" y="50" width="10" height="570" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="UY-EM7-1ECCvWtENr50b-73" value="Page 191 (Chapter 22) of Clean Architecture" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
|
||||||
|
<mxGeometry y="870" width="480" height="20" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="tM_Gde3HH8YiZ2frBV5J-2" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;dashed=1;endArrow=block;endFill=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="tM_Gde3HH8YiZ2frBV5J-0" target="tM_Gde3HH8YiZ2frBV5J-1" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="tM_Gde3HH8YiZ2frBV5J-4" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.75;exitY=0;exitDx=0;exitDy=0;entryX=0.75;entryY=1;entryDx=0;entryDy=0;dashed=1;endArrow=open;endFill=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="tM_Gde3HH8YiZ2frBV5J-0" target="tM_Gde3HH8YiZ2frBV5J-1" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="tM_Gde3HH8YiZ2frBV5J-0" value="MainPresenter" style="html=1;dashed=0;whitespace=wrap;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
|
||||||
|
<mxGeometry x="420" y="82.5" width="110" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="tM_Gde3HH8YiZ2frBV5J-1" value="&lt;&lt;Interface&gt;&gt;<br>IConsoleOutput" style="html=1;dashed=0;whitespace=wrap;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
|
||||||
|
<mxGeometry x="420" y="-20" width="110" height="50" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.InteractionAPI
|
||||||
|
{
|
||||||
|
public enum CommunicableType : uint
|
||||||
|
{
|
||||||
|
Disconnect,
|
||||||
|
Connect,
|
||||||
|
View,
|
||||||
|
Delta,
|
||||||
|
UnexpectedCommunication
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
namespace GameServiceWarden.InteractionAPI.Communicable.Requests
|
||||||
|
{
|
||||||
|
public struct ConnectRequest
|
||||||
|
{
|
||||||
|
public string requestedIdentifier;
|
||||||
|
public string programName;
|
||||||
|
public string programAuthor;
|
||||||
|
public string versionNumber;
|
||||||
|
public string details;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
using GameServiceWarden.InteractionAPI.Module;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.InteractionAPI.Communicable.Requests
|
||||||
|
{
|
||||||
|
public struct ServiceRequest
|
||||||
|
{
|
||||||
|
public ServiceManagerAction serviceManagerAction;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
namespace GameServiceWarden.InteractionAPI
|
||||||
|
{
|
||||||
|
public struct DisconnectRequest
|
||||||
|
{
|
||||||
|
public string reason;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO.Pipes;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.InteractionAPI.Communicable.Requests
|
||||||
|
{
|
||||||
|
public static class RequestHeader
|
||||||
|
{
|
||||||
|
public static void Decode(byte[] header, out CommunicableType type, out uint length) {
|
||||||
|
type = (CommunicableType) BitConverter.ToUInt32(header, 0);
|
||||||
|
length = BitConverter.ToUInt32(header, sizeof(uint));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.InteractionAPI.Communicable.Responses
|
||||||
|
{
|
||||||
|
public struct ConnectResponse
|
||||||
|
{
|
||||||
|
public string identifier;
|
||||||
|
public bool nameTaken;
|
||||||
|
public bool invalidName;
|
||||||
|
public string errorMsg;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
using GameServiceWarden.InteractionAPI.Module;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.InteractionAPI.Communicable.Responses
|
||||||
|
{
|
||||||
|
public struct DeltaResponse
|
||||||
|
{
|
||||||
|
public ServiceManagerTotal gameServiceDelta;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
namespace GameServiceWarden.InteractionAPI
|
||||||
|
{
|
||||||
|
public struct DisconnectResponse
|
||||||
|
{
|
||||||
|
public string reason;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.InteractionAPI.Communicable.Responses
|
||||||
|
{
|
||||||
|
public static class ResponseHeader
|
||||||
|
{
|
||||||
|
public static byte[] Encode(CommunicableType type, uint length) {
|
||||||
|
byte[] res = new byte[sizeof(uint) + sizeof(uint)];
|
||||||
|
BitConverter.GetBytes((uint)type).CopyTo(res, 0);
|
||||||
|
BitConverter.GetBytes(length).CopyTo(res, sizeof(uint));
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
namespace GameServiceWarden.InteractionAPI.Communicable.Responses
|
||||||
|
{
|
||||||
|
public struct UnexpectedRequestResponse
|
||||||
|
{
|
||||||
|
public CommunicableType origin;
|
||||||
|
public string message;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
using GameServiceWarden.InteractionAPI.Module;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.InteractionAPI.Communicable.Responses
|
||||||
|
{
|
||||||
|
public struct ViewResponse {
|
||||||
|
public ServiceManagerTotal state;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
@ -0,0 +1,24 @@
|
|||||||
|
namespace GameServiceWarden.InteractionAPI.Module
|
||||||
|
{
|
||||||
|
public struct ServiceManagerAction
|
||||||
|
{
|
||||||
|
public enum Type
|
||||||
|
{
|
||||||
|
|
||||||
|
Start,
|
||||||
|
Stop,
|
||||||
|
CreateService,
|
||||||
|
DeleteService,
|
||||||
|
ExecuteCommand,
|
||||||
|
SetServiceOption,
|
||||||
|
}
|
||||||
|
|
||||||
|
public string assemblyName;
|
||||||
|
public string moduleName;
|
||||||
|
public string serviceName;
|
||||||
|
public string option;
|
||||||
|
public string command;
|
||||||
|
public string value;
|
||||||
|
public Type action;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.InteractionAPI.Module
|
||||||
|
{
|
||||||
|
public struct ServiceManagerDelta
|
||||||
|
{
|
||||||
|
public bool subtract;
|
||||||
|
public string service;
|
||||||
|
public string running;
|
||||||
|
public string modules;
|
||||||
|
public byte[] logs;
|
||||||
|
public string optionName;
|
||||||
|
public string optionValue;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.InteractionAPI.Module
|
||||||
|
{
|
||||||
|
public struct ServiceManagerTotal
|
||||||
|
{
|
||||||
|
public ICollection<string> services;
|
||||||
|
public ICollection<string> running;
|
||||||
|
public ICollection<string> modules;
|
||||||
|
public IReadOnlyDictionary<string, byte[]> logs;
|
||||||
|
public IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> serviceOptions;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.ModuleFramework
|
||||||
|
{
|
||||||
|
public interface IService
|
||||||
|
{
|
||||||
|
event EventHandler<ServiceState> StateChangeEvent;
|
||||||
|
event EventHandler<string> UpdateLogEvent;
|
||||||
|
IReadOnlyCollection<IServiceConfigurable> Configurables{ get; }
|
||||||
|
void InitializeService();
|
||||||
|
void ElegantShutdown();
|
||||||
|
byte[] GetLogBuffer();
|
||||||
|
void ExecuteCommand(string command);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
namespace GameServiceWarden.ModuleFramework
|
||||||
|
{
|
||||||
|
public interface IServiceConfigurable
|
||||||
|
{
|
||||||
|
string OptionName { get; }
|
||||||
|
string GetValue();
|
||||||
|
bool SetValue(string value);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace GameServiceWarden.ModuleFramework
|
||||||
|
{
|
||||||
|
public interface IServiceModule
|
||||||
|
{
|
||||||
|
/// <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>
|
||||||
|
/// <param name="workspace">The workspace directory. All service required files should be stored here. Expect the directory to be created.</param>
|
||||||
|
/// <returns>The <see cref="IService"/> responsible for the instance of the game service.</returns>
|
||||||
|
IService InstantiateService(string workspace);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
namespace GameServiceWarden.ModuleFramework
|
||||||
|
{
|
||||||
|
public enum ServiceState
|
||||||
|
{
|
||||||
|
Stopped,
|
||||||
|
Running,
|
||||||
|
RestartPending
|
||||||
|
}
|
||||||
|
}
|
24
Jenkinsfile
vendored
Normal file
24
Jenkinsfile
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
pipeline {
|
||||||
|
agent {
|
||||||
|
kubernetes {
|
||||||
|
cloud 'Reslate Systems'
|
||||||
|
defaultContainer 'conda'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage ("Install") {
|
||||||
|
steps {
|
||||||
|
sh "conda update conda -y -q"
|
||||||
|
sh "conda env update -n base --file environment.yml -q"
|
||||||
|
sh "conda run -n base dotnet restore gameservicewarden.sln"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ("Test") {
|
||||||
|
steps {
|
||||||
|
sh returnStatus: true, script: 'conda run -n base dotnet test --logger xunit --no-restore gameservicewarden.sln'
|
||||||
|
xunit([xUnitDotNet(excludesPattern: '', pattern: 'GameServiceWarden.Tests/*.Tests/TestResults/*.xml', stopProcessingIfError: true)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
3
build.sh
Executable file
3
build.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
dotnet build -v n src/GameServiceWarden.ModuleFramework
|
||||||
|
dotnet build -v n src/GameServiceWarden.Core
|
9
environment.yml
Normal file
9
environment.yml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
name: gameservicewarden
|
||||||
|
channels:
|
||||||
|
- conda-forge
|
||||||
|
dependencies:
|
||||||
|
- dotnet
|
||||||
|
- icu
|
||||||
|
- tar
|
||||||
|
- zip
|
||||||
|
- nodejs
|
50
gameservicewarden.sln
Normal file
50
gameservicewarden.sln
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.0.31903.59
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GameServiceWarden", "GameServiceWarden", "{10373EAF-A78D-45C0-8F8E-FFA1F1D4DF33}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameServiceWarden.Core", "GameServiceWarden\GameServiceWarden.Core\GameServiceWarden.Core.csproj", "{3F7158B1-8C80-4529-9B1B-827329F699A7}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameServiceWarden.InteractionAPI", "GameServiceWarden\GameServiceWarden.InteractionAPI\GameServiceWarden.InteractionAPI.csproj", "{0DF20C89-C55C-401F-A607-A41A6EC6273E}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameServiceWarden.ModuleFramework", "GameServiceWarden\GameServiceWarden.ModuleFramework\GameServiceWarden.ModuleFramework.csproj", "{26B4678F-A48E-4A81-A304-3179D7C984E8}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GameServiceWarden.Tests", "GameServiceWarden.Tests", "{103BE93B-FB65-47AC-8741-90823A23A23E}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameServiceWarden.Core.Tests", "GameServiceWarden.Tests\GameServiceWarden.Core.Tests\GameServiceWarden.Core.Tests.csproj", "{48DD5E4F-1C9F-4642-AF7C-B43E91E83BB2}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{3F7158B1-8C80-4529-9B1B-827329F699A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{3F7158B1-8C80-4529-9B1B-827329F699A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3F7158B1-8C80-4529-9B1B-827329F699A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{3F7158B1-8C80-4529-9B1B-827329F699A7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{0DF20C89-C55C-401F-A607-A41A6EC6273E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{0DF20C89-C55C-401F-A607-A41A6EC6273E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{0DF20C89-C55C-401F-A607-A41A6EC6273E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{0DF20C89-C55C-401F-A607-A41A6EC6273E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{26B4678F-A48E-4A81-A304-3179D7C984E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{26B4678F-A48E-4A81-A304-3179D7C984E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{26B4678F-A48E-4A81-A304-3179D7C984E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{26B4678F-A48E-4A81-A304-3179D7C984E8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{48DD5E4F-1C9F-4642-AF7C-B43E91E83BB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{48DD5E4F-1C9F-4642-AF7C-B43E91E83BB2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{48DD5E4F-1C9F-4642-AF7C-B43E91E83BB2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{48DD5E4F-1C9F-4642-AF7C-B43E91E83BB2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{3F7158B1-8C80-4529-9B1B-827329F699A7} = {10373EAF-A78D-45C0-8F8E-FFA1F1D4DF33}
|
||||||
|
{0DF20C89-C55C-401F-A607-A41A6EC6273E} = {10373EAF-A78D-45C0-8F8E-FFA1F1D4DF33}
|
||||||
|
{26B4678F-A48E-4A81-A304-3179D7C984E8} = {10373EAF-A78D-45C0-8F8E-FFA1F1D4DF33}
|
||||||
|
{48DD5E4F-1C9F-4642-AF7C-B43E91E83BB2} = {103BE93B-FB65-47AC-8741-90823A23A23E}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
Reference in New Issue
Block a user