mirror of
https://github.com/MaSzyna-EU07/StarterNG.git
synced 2026-11-02 04:39:14 +01:00
Introduce layers, ports and a composition root
Ports in Application/Abstractions, adapters in Infrastructure, hand-wired AppServices (no reflection, as PublishAot requires). Adds a test project.
This commit is contained in:
15
StarterNG/Application/Abstractions/IClock.cs
Normal file
15
StarterNG/Application/Abstractions/IClock.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace StarterNG.Application.Abstractions;
|
||||
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Port over the system clock. Scenery weather defaults, timetable rendering and
|
||||
/// log timestamps all depend on "now"; routing them through a clock keeps those
|
||||
/// rules deterministic under test.
|
||||
/// </summary>
|
||||
public interface IClock
|
||||
{
|
||||
DateTime Now { get; }
|
||||
|
||||
DateTime UtcNow { get; }
|
||||
}
|
||||
25
StarterNG/Application/Abstractions/IDiagnosticsLog.cs
Normal file
25
StarterNG/Application/Abstractions/IDiagnosticsLog.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StarterNG.Application.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Port over the starter's error log. Domain and application code depends on
|
||||
/// this interface only; the file-backed implementation and the message box that
|
||||
/// surfaces faults to the user live in Infrastructure and Presentation.
|
||||
/// </summary>
|
||||
public interface IDiagnosticsLog
|
||||
{
|
||||
void Log(string message);
|
||||
|
||||
void Log(string context, Exception exception);
|
||||
|
||||
void Log(IEnumerable<string> messages);
|
||||
|
||||
/// <summary>Everything logged so far, newline separated.</summary>
|
||||
string Text { get; }
|
||||
|
||||
void Clear();
|
||||
|
||||
void Flush();
|
||||
}
|
||||
55
StarterNG/Application/Abstractions/IFileSystem.cs
Normal file
55
StarterNG/Application/Abstractions/IFileSystem.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace StarterNG.Application.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Port over the file system. Everything that reads or writes the MaSzyna
|
||||
/// installation goes through this interface instead of <see cref="File"/> and
|
||||
/// <see cref="Directory"/>, so parsers and use cases can be exercised against an
|
||||
/// in-memory installation in tests.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Kept deliberately narrow: only the operations the application actually
|
||||
/// performs are exposed. Paths are passed through verbatim to the adapter, which
|
||||
/// resolves them relative to the process working directory just like the
|
||||
/// framework methods it replaces.
|
||||
/// </remarks>
|
||||
public interface IFileSystem
|
||||
{
|
||||
bool FileExists(string path);
|
||||
|
||||
bool DirectoryExists(string path);
|
||||
|
||||
string ReadAllText(string path);
|
||||
|
||||
string ReadAllText(string path, Encoding encoding);
|
||||
|
||||
string[] ReadAllLines(string path);
|
||||
|
||||
Stream OpenRead(string path);
|
||||
|
||||
void WriteAllText(string path, string contents);
|
||||
|
||||
void WriteAllText(string path, string contents, Encoding encoding);
|
||||
|
||||
void WriteAllLines(string path, IEnumerable<string> lines);
|
||||
|
||||
void WriteAllBytes(string path, byte[] bytes);
|
||||
|
||||
void CreateDirectory(string path);
|
||||
|
||||
void DeleteFile(string path);
|
||||
|
||||
void CopyFile(string sourcePath, string destinationPath, bool overwrite);
|
||||
|
||||
/// <summary>Top-level files, empty when the directory does not exist.</summary>
|
||||
IReadOnlyList<string> GetFiles(string path, string searchPattern = "*");
|
||||
|
||||
/// <summary>Top-level directories, empty when the directory does not exist.</summary>
|
||||
IReadOnlyList<string> GetDirectories(string path);
|
||||
|
||||
DateTime GetLastWriteTimeUtc(string path);
|
||||
}
|
||||
34
StarterNG/Application/Abstractions/IGamePaths.cs
Normal file
34
StarterNG/Application/Abstractions/IGamePaths.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
namespace StarterNG.Application.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// The layout of a MaSzyna installation. Replaces the
|
||||
/// <c>Directory.GetCurrentDirectory()</c> calls and hard-coded "scenery/",
|
||||
/// "dynamic", "textures/mini/" literals that were previously spread across
|
||||
/// parsers, views and the loader.
|
||||
/// </summary>
|
||||
public interface IGamePaths
|
||||
{
|
||||
/// <summary>Installation root: the folder holding eu07.exe.</summary>
|
||||
string Root { get; }
|
||||
|
||||
/// <summary>Scenery definitions (*.scn).</summary>
|
||||
string Scenery { get; }
|
||||
|
||||
/// <summary>Rolling stock: dynamic/<maker>/<vehicle>/textures.txt.</summary>
|
||||
string Dynamic { get; }
|
||||
|
||||
/// <summary>Vehicle thumbnails used by the depot and the consist strip.</summary>
|
||||
string MiniTextures { get; }
|
||||
|
||||
/// <summary>Simulator data files (load_weights.txt and friends).</summary>
|
||||
string Data { get; }
|
||||
|
||||
/// <summary>Starter-owned configuration: presets, profiles, key bindings.</summary>
|
||||
string StarterConfig { get; }
|
||||
|
||||
/// <summary>The starter's own error log (starter/bledy.txt).</summary>
|
||||
string DiagnosticsLog { get; }
|
||||
|
||||
/// <summary>Resolves a path relative to <see cref="Root"/>.</summary>
|
||||
string FromRoot(params string[] segments);
|
||||
}
|
||||
22
StarterNG/Application/Abstractions/ILocalizedStrings.cs
Normal file
22
StarterNG/Application/Abstractions/ILocalizedStrings.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
namespace StarterNG.Application.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Port over the translation table. Anything that needs a user-facing string
|
||||
/// takes this, rather than reaching for the concrete localization service or a
|
||||
/// static on <c>App</c>.
|
||||
/// </summary>
|
||||
public interface ILocalizedStrings
|
||||
{
|
||||
/// <summary>
|
||||
/// Translation for a key. Missing keys return the key itself (and are logged
|
||||
/// once), so an untranslated screen stays usable.
|
||||
/// </summary>
|
||||
string this[string key] { get; }
|
||||
|
||||
/// <summary>Lookup for a key that may legitimately be absent; "" when missing.</summary>
|
||||
string Opt(string key);
|
||||
|
||||
string CurrentLanguage { get; }
|
||||
|
||||
string CurrentLangCode { get; }
|
||||
}
|
||||
42
StarterNG/Application/Abstractions/IProcessLauncher.cs
Normal file
42
StarterNG/Application/Abstractions/IProcessLauncher.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StarterNG.Application.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// A process the starter has launched or adopted. Exposes only what the starter
|
||||
/// needs — whether the simulator is still up, and a way to await its exit — so
|
||||
/// the presentation layer never handles <see cref="System.Diagnostics.Process"/>
|
||||
/// directly.
|
||||
/// </summary>
|
||||
public interface IProcessHandle
|
||||
{
|
||||
bool HasExited { get; }
|
||||
|
||||
Task WaitForExitAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Port over process creation: starting the simulator, opening a file, folder or
|
||||
/// URL in the desktop shell, and adopting an already running simulator.
|
||||
/// </summary>
|
||||
public interface IProcessLauncher
|
||||
{
|
||||
/// <summary>
|
||||
/// Starts an executable with the given arguments. Returns null when the
|
||||
/// process could not be created; <paramref name="error"/> then carries the
|
||||
/// reason for display.
|
||||
/// </summary>
|
||||
IProcessHandle? Start(string executablePath, IReadOnlyList<string> arguments, string? workingDirectory,
|
||||
out string? error);
|
||||
|
||||
/// <summary>
|
||||
/// Hands a path or URL to the desktop shell (explorer, xdg-open, ...).
|
||||
/// Returns false and logs when the shell refused it.
|
||||
/// </summary>
|
||||
bool OpenInShell(string pathOrUrl);
|
||||
|
||||
/// <summary>The first running process with that name, or null.</summary>
|
||||
IProcessHandle? FindRunning(string processName);
|
||||
}
|
||||
Reference in New Issue
Block a user