Client (C# / .NET 8 / WPF, MVVM via CommunityToolkit.Mvvm):
- PSLauncher.App: WPF UI dark theme (Epic-style sidebar + hero + big play button)
with UpdateAvailableDialog rendering Markdown release notes via Markdig.Wpf.
- PSLauncher.Core: services for installation registry (scans Proserve v{X.Y.Z}/),
process launcher, manifest fetch, SHA-256 integrity, HTTP download with
progress, ZIP install via .tmp + atomic rename, update orchestrator.
- PSLauncher.Models: RemoteManifest, InstalledVersion, LocalConfig DTOs.
Server (PHP 8 for OVH mutualisé, deployed under www/PS_Launcher/):
- Front controller + routes /manifest and /releasenotes/{version}.
- Static signed-manifest workflow with tools/sign-manifest.php CLI to
recompute SHA-256 and sizeBytes after each ZIP upload.
- .htaccess: HTTPS redirect, rewrite, security headers.
- config.example.php template; real config.php is gitignored.
Cohabiting versions: each release lives in its own Proserve v{version}/ folder
under installRoot. Old versions are never deleted automatically.
Roadmap: v0.3 = HTTP Range resume + Polly retry + state.json,
v0.4 = MySQL license + Ed25519 signatures + DPAPI, v0.5 = settings/UX polish.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
45 lines
1.5 KiB
C#
45 lines
1.5 KiB
C#
using System.Diagnostics;
|
|
using Microsoft.Extensions.Logging;
|
|
using PSLauncher.Models;
|
|
using SysProcess = System.Diagnostics.Process;
|
|
|
|
namespace PSLauncher.Core.Process;
|
|
|
|
public sealed class ProcessLauncher : IProcessLauncher
|
|
{
|
|
private readonly ILogger<ProcessLauncher> _logger;
|
|
|
|
public ProcessLauncher(ILogger<ProcessLauncher> logger) => _logger = logger;
|
|
|
|
public SysProcess Launch(InstalledVersion version, string[]? args = null)
|
|
{
|
|
if (!File.Exists(version.ExecutablePath))
|
|
throw new FileNotFoundException("Executable not found", version.ExecutablePath);
|
|
|
|
var psi = new ProcessStartInfo
|
|
{
|
|
FileName = version.ExecutablePath,
|
|
WorkingDirectory = version.FolderPath,
|
|
UseShellExecute = true
|
|
};
|
|
if (args is not null)
|
|
{
|
|
foreach (var arg in args) psi.ArgumentList.Add(arg);
|
|
}
|
|
|
|
_logger.LogInformation("Launching {Exe}", version.ExecutablePath);
|
|
return SysProcess.Start(psi)
|
|
?? throw new InvalidOperationException("Process.Start returned null");
|
|
}
|
|
|
|
public bool IsRunning(InstalledVersion version)
|
|
{
|
|
var name = Path.GetFileNameWithoutExtension(version.ExecutablePath);
|
|
return SysProcess.GetProcessesByName(name).Any(p =>
|
|
{
|
|
try { return string.Equals(p.MainModule?.FileName, version.ExecutablePath, StringComparison.OrdinalIgnoreCase); }
|
|
catch { return false; }
|
|
});
|
|
}
|
|
}
|