using System.Collections.ObjectModel; using System.ComponentModel; using System.Runtime.CompilerServices; namespace VideoDownloader.App.Browser.Features; /// CoreWebView2DownloadOperation 의 최소 추상화(단위 테스트 fake 교체 가능). public interface IDownloadOperation { string Uri { get; } string FileName { get; } long BytesReceived { get; } long TotalBytesToReceive { get; } /// InProgress / Interrupted / Completed / Cancelled string State { get; } void Pause(); void Resume(); void Cancel(); /// BytesReceived/State 변경 통지(구현은 호스트 스레드에서 발화). event Action? Changed; } /// 다운로드 매니저 UI 항목(T1-5) — 진행률/속도/상태를 INPC 로 노출. public sealed class PageDownloadItem : INotifyPropertyChanged { public IDownloadOperation Op { get; } public string FileName { get; } public string Uri { get; } public long TotalBytes { get; } private long _received; private long _bps; private string _state; public PageDownloadItem(IDownloadOperation op, string fileName) { Op = op; FileName = fileName; Uri = op.Uri; TotalBytes = op.TotalBytesToReceive; _received = op.BytesReceived; _state = op.State; op.Changed += () => { _received = op.BytesReceived; _state = op.State; OnPropertyChangedAll(); }; } public double Percent => TotalBytes > 0 ? Math.Clamp(_received * 100.0 / TotalBytes, 0, 100) : 0; public long BytesPerSecond => _bps; public string State => _state; public bool IsFinished => _state is "Completed" or "Interrupted" or "Cancelled"; public bool CanControl => _state == "InProgress"; public string SpeedText => _bps switch { <= 0 => "—", < 1024 => $"{(double)_bps:F1} B/s", < 1024 * 1024 => $"{_bps / 1024.0:F1} KB/s", _ => $"{_bps / 1024.0 / 1024.0:F1} MB/s", }; private long? _lastSpeedReceived; /// 속도 갱신: (현재까지 수신 바이트, 직전 호출로부터 경과). public void UpdateSpeed(long receivedNow, TimeSpan elapsed) { _received = receivedNow; if (elapsed.TotalSeconds > 0) { var delta = Math.Max(0, receivedNow - (_lastSpeedReceived ?? 0)); _bps = (long)(delta / elapsed.TotalSeconds); } _lastSpeedReceived = receivedNow; OnPropertyChangedAll(); } public event PropertyChangedEventHandler? PropertyChanged; private void OnPropertyChangedAll() { foreach (var n in new[] { nameof(Percent), nameof(BytesPerSecond), nameof(SpeedText), nameof(State), nameof(IsFinished), nameof(CanControl) }) PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(n)); } } /// 페이지 다운로드 목록(T1-5) — 최신순, 일시정지/재개/취소, 완료 정리. public sealed class PageDownloadTracker { public ObservableCollection Items { get; } = new(); public PageDownloadItem Add(IDownloadOperation op, string fileName) { var item = new PageDownloadItem(op, fileName); Items.Insert(0, item); return item; } public void Pause(PageDownloadItem item) => item.Op.Pause(); public void Resume(PageDownloadItem item) => item.Op.Resume(); public void Cancel(PageDownloadItem item) => item.Op.Cancel(); public void ClearCompleted() { foreach (var done in Items.Where(i => i.IsFinished).ToList()) Items.Remove(done); } }