From d5767050f2d9698c6e6dcd8a219a0df668ab7d33 Mon Sep 17 00:00:00 2001 From: Ednunp <29843396+Ednunp@users.noreply.github.com> Date: Mon, 13 Jul 2026 09:51:59 +0100 Subject: [PATCH] Service: show the running version in the Service menu (so a self-update is visible) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Local checkpoint - NOT for public release. Answers "how will we know if the service updated itself?" - The service records its version + start time on every OnStart into ServiceStore (ProgramData). A self-update restart therefore bumps the version and refreshes the start time. - The Service menu status line now shows it: "Service: installed, running — version 5.3, running since 2 min ago". So you can see the running version at a glance, and a recent start with a bumped version is the self-update landing. - Also logged on start ("service: OnStart, version X") for the service log. Status round-trip covered by the isolation self-test. Gate 27/27. Co-Authored-By: Claude Opus 4.8 --- src/RemSound.App/MainForm.cs | 16 ++++++++++++++++ src/RemSound.App/RemSoundService.cs | 7 ++++++- src/RemSound.App/SelfTest.cs | 4 ++++ src/RemSound.Core/ServiceStore.cs | 23 +++++++++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/RemSound.App/MainForm.cs b/src/RemSound.App/MainForm.cs index f37bb7b..5d75536 100644 --- a/src/RemSound.App/MainForm.cs +++ b/src/RemSound.App/MainForm.cs @@ -2205,6 +2205,12 @@ public sealed class MainForm : Form { var state = ServiceControl.Query(); status.Text = "Service: " + DescribeServiceState(state); + // Surface the running version + when it (re)started, so a self-update is visible at a glance. + if (state is ServiceState.Running or ServiceState.Stopped && ServiceStore.LoadStatus() is { Version: { } ver } st) + { + status.Text += $" — version {ver}"; + if (state == ServiceState.Running && st.StartedUtc != default) status.Text += $", running since {DescribeAgo(st.StartedUtc)}"; + } var installed = state != ServiceState.NotInstalled; install.Enabled = !installed; uninstall.Enabled = installed; @@ -2214,6 +2220,16 @@ public sealed class MainForm : Form return serviceMenu; } + private static string DescribeAgo(DateTime utc) + { + var span = DateTime.UtcNow - utc; + if (span < TimeSpan.Zero) span = TimeSpan.Zero; + if (span.TotalMinutes < 1) return "just now"; + if (span.TotalHours < 1) return $"{(int)span.TotalMinutes} min ago"; + if (span.TotalDays < 1) return $"{(int)span.TotalHours} h ago"; + return $"{(int)span.TotalDays} d ago"; + } + private static string DescribeServiceState(ServiceState s) => s switch { ServiceState.NotInstalled => "not installed", diff --git a/src/RemSound.App/RemSoundService.cs b/src/RemSound.App/RemSoundService.cs index a1e1f46..ac9381d 100644 --- a/src/RemSound.App/RemSoundService.cs +++ b/src/RemSound.App/RemSoundService.cs @@ -40,7 +40,12 @@ public sealed class RemSoundService : ServiceBase protected override void OnStart(string[] args) { log = new RemSoundLog { Enabled = SafeServiceLogging() }; - log.Event("service: OnStart"); + var version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; + var versionText = version is null ? "?" : $"{version.Major}.{version.Minor}"; + log.Event($"service: OnStart, version {versionText}"); + // Record the running version + start time so the app's Service menu can show them — this is how a + // self-update is visible (the version bumps and the start time is recent). + try { ServiceStore.SaveStatus(new ServiceStore.ServiceStatus { Version = versionText, StartedUtc = DateTime.UtcNow }); } catch { } host = ServiceSendHost.FromConfig(msg => log?.Event(msg)); worker = new Thread(() => { diff --git a/src/RemSound.App/SelfTest.cs b/src/RemSound.App/SelfTest.cs index 5b5c7f7..adc7ee7 100644 --- a/src/RemSound.App/SelfTest.cs +++ b/src/RemSound.App/SelfTest.cs @@ -732,6 +732,10 @@ internal static class SelfTest Check(back is not null && back.WasapiSendMode == "applications" && back.SelectedConnectedPeers.Contains("10.0.0.5"), "the service profile must round-trip through the machine-wide store"); Check(ServiceStore.LoadLoggingEnabled(), "service logging flag must round-trip"); + + // Running status (version + start time) round-trips — this is what the Service menu shows. + ServiceStore.SaveStatus(new ServiceStore.ServiceStatus { Version = "5.3", StartedUtc = DateTime.UtcNow }); + Check(ServiceStore.LoadStatus()?.Version == "5.3", "the service running-status version must round-trip"); } finally { ServiceStore.TestDirectoryOverride = saved; } diff --git a/src/RemSound.Core/ServiceStore.cs b/src/RemSound.Core/ServiceStore.cs index 140a4f0..76ae3d4 100644 --- a/src/RemSound.Core/ServiceStore.cs +++ b/src/RemSound.Core/ServiceStore.cs @@ -66,5 +66,28 @@ public static class ServiceStore /// True once a service profile has been configured. public static bool IsConfigured() => File.Exists(ProfilePath); + // === Running status (written by the service on start, read by the app's Service menu) === + private static string StatusPath => Path.Combine(Directory, "status.json"); + + /// The version + start time the service last recorded — so the interactive app can show + /// which version is running and when it (re)started, which is how you SEE a self-update land. + public sealed class ServiceStatus + { + public string? Version { get; set; } + public DateTime StartedUtc { get; set; } + } + + public static void SaveStatus(ServiceStatus status) + { + try { System.IO.Directory.CreateDirectory(Directory); File.WriteAllText(StatusPath, JsonSerializer.Serialize(status)); } + catch { /* best-effort */ } + } + + public static ServiceStatus? LoadStatus() + { + try { return File.Exists(StatusPath) ? JsonSerializer.Deserialize(File.ReadAllText(StatusPath)) : null; } + catch { return null; } + } + private sealed class ServiceSettings { public bool LoggingEnabled { get; set; } } }