315 lines
13 KiB
C#
315 lines
13 KiB
C#
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Net.Http;
|
|
|
|
namespace MijnBesluit.Uitroller;
|
|
|
|
/// <summary>
|
|
/// De testversie op deze pc: MijnBesluit.Web bouwen, starten, en Chrome openen op de site.
|
|
///
|
|
/// Hetzelfde als F5 in Visual Studio, maar zonder Visual Studio: bouwen met
|
|
/// <c>dotnet build</c>, starten met <c>dotnet run</c> en het profiel uit
|
|
/// launchSettings.json (Development, dus de user secrets doen mee), en pas als de site
|
|
/// antwoordt de browser openen. Zo krijg je geen "deze site is niet bereikbaar" omdat
|
|
/// Chrome sneller was dan de site.
|
|
///
|
|
/// De site blijft draaien tot je op "Test stoppen" drukt of het venster sluit.
|
|
/// </summary>
|
|
public sealed class Lokaaltest : IDisposable
|
|
{
|
|
private readonly Instellingen _instellingen;
|
|
private readonly Action<string, bool> _meld;
|
|
private Process? _site;
|
|
|
|
/// <summary>De Chrome van de test: een eigen exemplaar met een eigen profiel, zodat
|
|
/// Test stoppen precies dat venster kan sluiten en niet de Chrome waarin je werkt.</summary>
|
|
private Process? _browser;
|
|
|
|
public Lokaaltest(Instellingen instellingen, Action<string, bool> meld)
|
|
{
|
|
_instellingen = instellingen;
|
|
_meld = meld;
|
|
}
|
|
|
|
public bool Draait => _site is { HasExited: false };
|
|
|
|
/// <summary>Gaat af als de site stopt, ook als hij vanzelf stopt; het venster zet
|
|
/// dan de knop terug op "Lokaal testen".</summary>
|
|
public event Action? Gestopt;
|
|
|
|
private string Webproject => Path.Combine(_instellingen.Projectmap, "src", "MijnBesluit.Web");
|
|
|
|
/// <summary>Voor welke weergave de laatst gestarte test Chrome heeft geopend (desktop of
|
|
/// mobiel/smal venster) - onthouden zodat StopAsync/Dispose niets extra's hoeft te weten.</summary>
|
|
private bool _mobiel;
|
|
|
|
/// <summary>
|
|
/// Start de testversie. <paramref name="mobiel"/> bepaalt alleen het Chrome-venster: bij
|
|
/// <c>true</c> opent Chrome smal (telefoonformaat, zoals RenH's WM-testen), bij <c>false</c>
|
|
/// normaal (desktopformaat, zoals voorheen). De site zelf bouwt en start hetzelfde in beide
|
|
/// gevallen - dit is geen apart profiel, alleen een ander Chrome-venster.
|
|
/// </summary>
|
|
public async Task<bool> StartAsync(CancellationToken ct, bool mobiel = false)
|
|
{
|
|
_mobiel = mobiel;
|
|
await StopAsync();
|
|
|
|
if (!Directory.Exists(Webproject))
|
|
{
|
|
_meld($"Het webproject staat er niet: {Webproject}", true);
|
|
return false;
|
|
}
|
|
|
|
// Draait er al iets op dat adres — meestal Visual Studio — dan niet bouwen:
|
|
// de dll's zitten dan vast. Alleen de browser openen.
|
|
if (await AntwoordtAsync(ct))
|
|
{
|
|
_meld($"Er draait al een MijnBesluit op {_instellingen.Testadres} (Visual Studio?). " +
|
|
"Ik open alleen de browser.", false);
|
|
OpenBrowser();
|
|
return true;
|
|
}
|
|
|
|
_meld("MijnBesluit.Web bouwen (Debug) …", false);
|
|
if (!await DraaiAsync("dotnet", $"build \"{Webproject}\" -c Debug --nologo -v minimal", ct))
|
|
{
|
|
_meld("Bouwen is mislukt; zie de regels hierboven.", true);
|
|
return false;
|
|
}
|
|
|
|
_meld("MijnBesluit.Web starten …", false);
|
|
var start = new ProcessStartInfo
|
|
{
|
|
FileName = "dotnet",
|
|
Arguments = $"run --project \"{Webproject}\" --no-build --launch-profile https",
|
|
WorkingDirectory = Webproject,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true
|
|
};
|
|
|
|
_site = new Process { StartInfo = start, EnableRaisingEvents = true };
|
|
_site.OutputDataReceived += (_, e) => { if (!string.IsNullOrWhiteSpace(e.Data)) _meld("site: " + e.Data, false); };
|
|
_site.ErrorDataReceived += (_, e) => { if (!string.IsNullOrWhiteSpace(e.Data)) _meld("site: " + e.Data, true); };
|
|
_site.Exited += (_, _) => { _meld("De testversie is gestopt.", false); Gestopt?.Invoke(); };
|
|
|
|
try
|
|
{
|
|
_site.Start();
|
|
_site.BeginOutputReadLine();
|
|
_site.BeginErrorReadLine();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_meld($"Kon dotnet niet starten: {ex.Message}", true);
|
|
_site = null;
|
|
return false;
|
|
}
|
|
|
|
// Wachten tot hij antwoordt, maximaal een minuut.
|
|
for (var i = 0; i < 120; i++)
|
|
{
|
|
if (ct.IsCancellationRequested) { await StopAsync(); return false; }
|
|
if (_site.HasExited)
|
|
{
|
|
_meld("De site stopte meteen weer. Kijk naar de regels met \"site:\" hierboven.", true);
|
|
return false;
|
|
}
|
|
if (await AntwoordtAsync(ct)) break;
|
|
await Task.Delay(500, ct);
|
|
}
|
|
|
|
if (!await AntwoordtAsync(ct))
|
|
{
|
|
_meld($"De site antwoordt na een minuut nog niet op {_instellingen.Testadres}.", true);
|
|
return false;
|
|
}
|
|
|
|
_meld($"De site draait op {_instellingen.Testadres}.", false);
|
|
OpenBrowser();
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stopt de site. Buiten de UI-thread: het afschieten van de procesboom en het
|
|
/// afronden van de uitvoer kan even duren, en zolang dat op de UI-thread gebeurt
|
|
/// staat het venster stil — en als de uitvoer dan nog een regel in het logboek wil
|
|
/// zetten, wacht die op de UI-thread die op hem wacht. Dat was het vastlopen.
|
|
/// </summary>
|
|
public async Task StopAsync()
|
|
{
|
|
SluitBrowser();
|
|
|
|
var site = _site;
|
|
_site = null;
|
|
if (site is null) return;
|
|
|
|
await Task.Run(() =>
|
|
{
|
|
try
|
|
{
|
|
site.CancelOutputRead();
|
|
site.CancelErrorRead();
|
|
}
|
|
catch (Exception) { /* lezen was al gestopt */ }
|
|
|
|
try
|
|
{
|
|
if (!site.HasExited)
|
|
{
|
|
// Met de hele boom: dotnet run start zelf nog een proces (MijnBesluit.Web.exe).
|
|
site.Kill(entireProcessTree: true);
|
|
site.WaitForExit(5000);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_meld($"Stoppen lukte niet helemaal: {ex.Message}", true);
|
|
}
|
|
finally
|
|
{
|
|
site.Dispose();
|
|
}
|
|
});
|
|
|
|
_meld("Testversie gestopt.", false);
|
|
}
|
|
|
|
/// <summary>Bij het sluiten van het venster: niet wachten, alleen afschieten.</summary>
|
|
public void Dispose()
|
|
{
|
|
SluitBrowser();
|
|
var site = _site;
|
|
_site = null;
|
|
if (site is null) return;
|
|
try { if (!site.HasExited) site.Kill(entireProcessTree: true); } catch (Exception) { }
|
|
site.Dispose();
|
|
}
|
|
|
|
|
|
private async Task<bool> AntwoordtAsync(CancellationToken ct)
|
|
{
|
|
try
|
|
{
|
|
// Het ontwikkelcertificaat van localhost hoeft hier niet gecontroleerd te
|
|
// worden: we vragen alleen of er iets luistert. Alleen voor localhost.
|
|
using var handler = new HttpClientHandler
|
|
{
|
|
AllowAutoRedirect = false,
|
|
ServerCertificateCustomValidationCallback = (bericht, _, _, _) =>
|
|
bericht.RequestUri?.IsLoopback == true
|
|
};
|
|
using var http = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(2) };
|
|
using var antwoord = await http.GetAsync(_instellingen.Testadres.TrimEnd('/') + "/inloggen", ct);
|
|
return true; // elk antwoord telt: 200, een doorverwijzing, zelfs een fout — er luistert iets
|
|
}
|
|
catch (Exception) when (!ct.IsCancellationRequested)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>Chrome, zoals gevraagd. Staat Chrome er niet, dan de standaardbrowser.</summary>
|
|
private void OpenBrowser()
|
|
{
|
|
var adres = _instellingen.Testadres.TrimEnd('/') + "/";
|
|
|
|
// Het e-mailadres alvast meegeven, zodat het op de inlogpagina ingevuld staat (de verse
|
|
// profielmap heeft geen onthoud-cookie). Alleen invullen, niet inloggen.
|
|
if (!string.IsNullOrWhiteSpace(_instellingen.Emailadres))
|
|
adres += "?email=" + Uri.EscapeDataString(_instellingen.Emailadres.Trim());
|
|
|
|
var chrome = new[]
|
|
{
|
|
_instellingen.Chrome,
|
|
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Google\Chrome\Application\chrome.exe"),
|
|
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), @"Google\Chrome\Application\chrome.exe"),
|
|
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Google\Chrome\Application\chrome.exe")
|
|
}.FirstOrDefault(p => !string.IsNullOrWhiteSpace(p) && File.Exists(p));
|
|
|
|
try
|
|
{
|
|
if (chrome is not null)
|
|
{
|
|
// Eigen profielmap: dan is het een apart Chrome-exemplaar dat we bij
|
|
// Test stoppen kunnen sluiten. De map blijft bestaan, dus je blijft
|
|
// ingelogd en instellingen (zoals de splitters) worden onthouden.
|
|
// Mobiel krijgt een eigen profielmap, anders vecht die om dezelfde
|
|
// (onthouden) venstergrootte als de desktop-test.
|
|
var profiel = Path.Combine(
|
|
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
|
"MijnBesluitTools", _mobiel ? "ChromeTestMobiel" : "ChromeTest");
|
|
var nieuw = !Directory.Exists(profiel);
|
|
Directory.CreateDirectory(profiel);
|
|
var start = new ProcessStartInfo(chrome) { UseShellExecute = false };
|
|
start.ArgumentList.Add($"--user-data-dir={profiel}");
|
|
start.ArgumentList.Add("--no-first-run");
|
|
start.ArgumentList.Add("--no-default-browser-check");
|
|
start.ArgumentList.Add("--new-window");
|
|
if (_mobiel)
|
|
{
|
|
// Smal venster (telefoonformaat), zodat de site zijn mobiele/responsieve
|
|
// weergave laat zien. Dit is venstergrootte, geen echte devicetools-emulatie
|
|
// (geen andere user-agent of device-pixel-ratio) - net als bij RenH's WM-testen.
|
|
start.ArgumentList.Add("--window-size=412,915");
|
|
start.ArgumentList.Add("--window-position=120,40");
|
|
}
|
|
else if (nieuw)
|
|
{
|
|
// Alleen de allereerste keer een vaste maat/positie meegeven; daarna onthoudt
|
|
// Chrome zelf de laatst gebruikte venstergrootte in deze profielmap.
|
|
start.ArgumentList.Add("--window-size=1366,900");
|
|
start.ArgumentList.Add("--window-position=60,40");
|
|
}
|
|
start.ArgumentList.Add(adres);
|
|
_browser = Process.Start(start);
|
|
}
|
|
else
|
|
{
|
|
_meld("Chrome niet gevonden; de standaardbrowser wordt gebruikt.", false);
|
|
Process.Start(new ProcessStartInfo(adres) { UseShellExecute = true });
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_meld($"De browser kon niet worden gestart: {ex.Message}. Open zelf {adres}", true);
|
|
}
|
|
}
|
|
|
|
/// <summary>Sluit de test-Chrome (alleen die, met al zijn tabbladen en processen).
|
|
/// Bij de standaardbrowser weten we niet welk tabblad het is; die blijft open.</summary>
|
|
private void SluitBrowser()
|
|
{
|
|
var browser = _browser;
|
|
_browser = null;
|
|
if (browser is null) return;
|
|
try { if (!browser.HasExited) browser.Kill(entireProcessTree: true); }
|
|
catch (Exception) { /* al dicht */ }
|
|
finally { browser.Dispose(); }
|
|
}
|
|
|
|
private async Task<bool> DraaiAsync(string programma, string argumenten, CancellationToken ct)
|
|
{
|
|
var start = new ProcessStartInfo
|
|
{
|
|
FileName = programma,
|
|
Arguments = argumenten,
|
|
WorkingDirectory = _instellingen.Projectmap,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true
|
|
};
|
|
|
|
using var proces = new Process { StartInfo = start };
|
|
proces.OutputDataReceived += (_, e) => { if (!string.IsNullOrWhiteSpace(e.Data)) _meld(e.Data, false); };
|
|
proces.ErrorDataReceived += (_, e) => { if (!string.IsNullOrWhiteSpace(e.Data)) _meld(e.Data, true); };
|
|
proces.Start();
|
|
proces.BeginOutputReadLine();
|
|
proces.BeginErrorReadLine();
|
|
await proces.WaitForExitAsync(ct);
|
|
return proces.ExitCode == 0;
|
|
}
|
|
}
|