From 5c491c9d83706e168b3a7e25cf93f981a139590b Mon Sep 17 00:00:00 2001 From: Zac Gaetano Date: Sun, 10 May 2026 21:19:34 -0400 Subject: [PATCH] Footer shows recording drive free space MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Operators recording long shows previously had to open File Explorer to check disk pressure. New '· 245 GB free' indicator next to the REC badge polls DriveInfo on the recording drive at the existing 1Hz stats tick. Coral tint kicks in below 10GB; existing DiskSpaceWatcher still auto-disables recording at 1GB as a hard safety net. FormatBytes helper produces footer-readable strings: '1.2 TB' / '245 GB' (no decimal for 100+ GB to avoid clutter) / '8.4 GB' (decimal for the low-warning case) / '450 MB'. Polling is wrapped in try/catch — network paths occasionally throw, and disk-space display is a comfort feature, not a critical signal. --- src/TeamsISO.App/MainWindow.xaml | 23 ++++++- src/TeamsISO.App/ViewModels/MainViewModel.cs | 64 ++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/TeamsISO.App/MainWindow.xaml b/src/TeamsISO.App/MainWindow.xaml index a787a96..cfe66b7 100644 --- a/src/TeamsISO.App/MainWindow.xaml +++ b/src/TeamsISO.App/MainWindow.xaml @@ -367,12 +367,33 @@ + VerticalAlignment="Center" + Margin="0,0,8,0"> + + + + + + + + + diff --git a/src/TeamsISO.App/ViewModels/MainViewModel.cs b/src/TeamsISO.App/ViewModels/MainViewModel.cs index 223c2d5..37c79e9 100644 --- a/src/TeamsISO.App/ViewModels/MainViewModel.cs +++ b/src/TeamsISO.App/ViewModels/MainViewModel.cs @@ -221,6 +221,26 @@ public sealed class MainViewModel : ObservableObject, IDisposable private string _recordingElapsed = string.Empty; private DateTimeOffset? _recordingStartedAt; + /// + /// Free disk space on the recording drive, formatted as "245 GB" / + /// "1.2 TB" / "8.4 GB". Empty when recording isn't enabled or the + /// recording path is invalid. Polled at the existing 1Hz stats tick. + /// + public string RecordingFreeSpace + { + get => _recordingFreeSpace; + private set => SetField(ref _recordingFreeSpace, value); + } + private string _recordingFreeSpace = string.Empty; + + /// True when free disk space drops below 10GB — UI cues coral. + public bool IsLowDiskSpace + { + get => _isLowDiskSpace; + private set => SetField(ref _isLowDiskSpace, value); + } + private bool _isLowDiskSpace; + /// True when the REST control surface (or OSC bridge, or both) is listening. public bool IsControlSurfaceRunning { @@ -555,6 +575,23 @@ public sealed class MainViewModel : ObservableObject, IDisposable Toast.Show($"Stopped {enabled.Length} ISO(s)"); } + /// + /// Format a byte count as a human-readable string with 0-1 decimal + /// places — e.g. "8.4 GB", "245 GB", "1.2 TB". Optimized for footer + /// readability over precision: nobody needs to know it's 245.34 GB. + /// + private static string FormatBytes(long bytes) + { + if (bytes < 0) return "—"; + const long GB = 1024L * 1024 * 1024; + const long TB = GB * 1024; + if (bytes >= TB) return $"{bytes / (double)TB:0.0} TB"; + if (bytes >= GB) return bytes >= 100 * GB + ? $"{bytes / GB} GB" // 100+ GB: no decimal — clutter + : $"{bytes / (double)GB:0.0} GB"; // < 100GB: one decimal for the low-warning case + return $"{bytes / 1024 / 1024} MB"; + } + /// /// Pull the meaningful "meeting title" out of Teams' raw window title. /// Teams uses formats like: @@ -628,11 +665,38 @@ public sealed class MainViewModel : ObservableObject, IDisposable RecordingElapsed = recElapsed.TotalHours >= 1 ? $"{(int)recElapsed.TotalHours:D2}:{recElapsed.Minutes:D2}:{recElapsed.Seconds:D2}" : $"{recElapsed.Minutes:D2}:{recElapsed.Seconds:D2}"; + + // Disk free on the recording drive. DriveInfo is cheap (a + // single GetDiskFreeSpaceEx call). We tolerate any failure + // path silently — disk-space display is a comfort feature, not + // a critical signal, and the existing DiskSpaceWatcher already + // handles the auto-disable-at-1GB safety net. + try + { + var dir = Settings.RecordingDirectory; + if (!string.IsNullOrEmpty(dir)) + { + var root = System.IO.Path.GetPathRoot(dir); + if (!string.IsNullOrEmpty(root)) + { + var drive = new System.IO.DriveInfo(root); + if (drive.IsReady) + { + var freeBytes = drive.AvailableFreeSpace; + RecordingFreeSpace = FormatBytes(freeBytes); + IsLowDiskSpace = freeBytes < 10L * 1024 * 1024 * 1024; // <10GB + } + } + } + } + catch { /* defensive — drive enumeration occasionally throws on network paths */ } } else { _recordingStartedAt = null; RecordingElapsed = string.Empty; + RecordingFreeSpace = string.Empty; + IsLowDiskSpace = false; } // Session timer — start on first ISO going live, reset when none are