dragon-iso/src/Dragon-ISO.App/ViewModels/MainViewModel.BulkCommands.cs
Zac Gaetano edb7975039
Some checks failed
CI / build-and-test (push) Failing after 29s
rebrand: rename all TeamsISO source paths to Dragon-ISO
- Rename solution files: TeamsISO.sln/slnf -> Dragon-ISO.sln/slnf
- Rename all src/TeamsISO.* directories and project files
  to src/Dragon-ISO.* equivalents
- Update .gitignore to exclude build/test output logs
- Update ci.yml, CHANGELOG.md, build-and-test.ps1, docs references

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 11:18:27 -04:00

149 lines
5.9 KiB
C#

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
{
/// <summary>
/// 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.
/// </summary>
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)");
}
/// <summary>
/// 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.
/// </summary>
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)");
}
/// <summary>
/// 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.
/// </summary>
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)}");
}
}