using DragonISO.App.Services; namespace DragonISO.App.ViewModels; // Bulk operations that touch every (or every-enabled) participant — // Stop all ISOs, Enable all online, Snapshot all enabled frames. // Split out of MainViewModel.cs so the main file isn't dominated by // long async iteration loops. // // The RecordingCommands partial originally planned at this slot is // intentionally absent: the recording surface was axed earlier in the // May 2026 batch (see commit 1d1ce6a). What remains is bulk-state // manipulation across the participants collection. public sealed partial class MainViewModel { /// /// Enable ISOs for every online + non-enabled participant in /// parallel-ish (sequential await, but each individual EnableIsoAsync /// is fast). Tolerates per-participant failures so one bad source /// doesn't abort the rest. /// private async Task EnableAllOnlineAsync() { var candidates = Participants.Where(p => p.IsOnline && !p.IsEnabled).ToArray(); var enabled = 0; foreach (var p in candidates) { try { var resolvedName = string.IsNullOrWhiteSpace(p.CustomName) ? OutputNameTemplate.Render( OutputNameTemplate.Get(), p.Id, p.DisplayName) : p.CustomName; // 3-arg overload (no recordOverride) — recording surface axed, // so the engine's per-pipeline recorder sink stays unattached. await _controller.EnableIsoAsync(p.Id, resolvedName, CancellationToken.None); p.IsEnabled = true; enabled++; } catch { // Per-participant best-effort — one bad source shouldn't // abort the bulk operation. } } Toast.Show(enabled == 0 ? "No participants to enable" : $"Enabled {enabled} ISO(s)"); } /// /// Emergency-stop: disable every running ISO. Confirmation dialog with /// default-No guards mid-show misclicks; the regret cost of yanking 5 /// ISOs is far higher than the Enter-press cost of the prompt. /// private async Task StopAllIsosAsync() { // Snapshot first so the collection doesn't mutate while we iterate. var enabled = Participants.Where(p => p.IsEnabled).ToArray(); if (enabled.Length == 0) { Toast.Show("No ISOs to stop"); return; } var confirm = System.Windows.MessageBox.Show( $"Stop {enabled.Length} running ISO(s)?\n\nThis tears down every active pipeline immediately.", "Dragon-ISO — Stop all ISOs", System.Windows.MessageBoxButton.YesNo, System.Windows.MessageBoxImage.Warning, System.Windows.MessageBoxResult.No); if (confirm != System.Windows.MessageBoxResult.Yes) return; foreach (var p in enabled) { try { await _controller.DisableIsoAsync(p.Id, CancellationToken.None); } catch { /* defensive */ } p.IsEnabled = false; } Toast.Show($"Stopped {enabled.Length} ISO(s)"); } /// /// Save a PNG of every currently-enabled participant's latest /// processed frame to a timestamped subdirectory under /// %USERPROFILE%\Pictures\Dragon-ISO\. One folder per Snapshot All click /// so back-to-back clicks don't comingle. Useful for end-of-meeting /// archives, recapping who showed up, etc. /// private void SnapshotAll() { var enabled = Participants.Where(p => p.IsEnabled).ToArray(); if (enabled.Length == 0) { Toast.Warn("No enabled participants to snapshot"); return; } var rootDir = System.IO.Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Dragon-ISO", $"snapshots-{DateTimeOffset.Now:yyyyMMdd_HHmmss}"); try { System.IO.Directory.CreateDirectory(rootDir); } catch (Exception ex) { Toast.Warn($"Couldn't create snapshot dir: {ex.Message}"); return; } var saved = 0; var failed = 0; foreach (var p in enabled) { try { var frame = _controller.GetLatestProcessedFrame(p.Id); if (frame is null || frame.Pixels.IsEmpty) { failed++; continue; } var safeName = string.Join("_", p.DisplayName.Split(System.IO.Path.GetInvalidFileNameChars())); if (string.IsNullOrWhiteSpace(safeName)) safeName = "participant"; var path = System.IO.Path.Combine(rootDir, $"{safeName}.png"); var stride = frame.Width * 4; var bmp = new System.Windows.Media.Imaging.WriteableBitmap( frame.Width, frame.Height, 96, 96, System.Windows.Media.PixelFormats.Bgra32, null); bmp.WritePixels( new System.Windows.Int32Rect(0, 0, frame.Width, frame.Height), frame.Pixels.ToArray(), stride, 0); using var fs = new System.IO.FileStream(path, System.IO.FileMode.Create); var encoder = new System.Windows.Media.Imaging.PngBitmapEncoder(); encoder.Frames.Add(System.Windows.Media.Imaging.BitmapFrame.Create(bmp)); encoder.Save(fs); saved++; } catch { failed++; } } Toast.Show(failed > 0 ? $"Saved {saved} snapshot(s) ({failed} failed) to Pictures\\Dragon-ISO\\{System.IO.Path.GetFileName(rootDir)}" : $"Saved {saved} snapshot(s) to Pictures\\Dragon-ISO\\{System.IO.Path.GetFileName(rootDir)}"); } }