UI 복잡 버전 234 RED→GREEN 완료(docs/UI_USECASES.md §A~§J): 라이브 테마 전환(DynamicResource 전환+ThemeResolver+HighContrast 테마, 재시작 없음), 디자인 토큰 체계·WCAG 대비 결함 5건 수정, 설정창 카테고리 내비/검색 재구성+AppConfig 40여 속성, 다운로드 관리자(속도/ETA/세그먼트맵/전체 일시정지-이어받기/CSV 내보내기/예약 게이트/중복 확인), 라이브러리·아카이브·작업공간·재생목록·즐겨찾기 편집 뷰, 접근성(자동화 속성 30여 개·커스텀 피어·docs/ACCESSIBILITY.md), 인터랙션(휠 클릭 새 탭·XButton 탐색·F12·영역 캡처·프로필 전환), 상태/알림(토스트·알림센터·온보딩·CrashReportDialog). 보강 테스트 35건+실창 E2E 4건(StaDispatcher 기반 — 프록시 설정이 기시작 핸들러를 건드리는 크래시 실결함 수정 포함) — 453/453 GREEN. 이전 미커밋 작업(CI 워크플로·Mdns·QueueHub·UpdateChecker·AGENTS/CLAUDE·빌드 스크립트) 일괄 포함
This commit is contained in:
parent
5a0ce730b2
commit
17f2f91152
88 changed files with 9463 additions and 1127 deletions
57
VideoDownloader.App/Services/DownloadScheduler.cs
Normal file
57
VideoDownloader.App/Services/DownloadScheduler.cs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
namespace VideoDownloader.App.Services;
|
||||
|
||||
/// <summary>
|
||||
/// UC-D21 다운로드 예약 — 지정 시간대(예: 야간 02:00~08:00)에만 다운로드를 허용하는 게이트.
|
||||
/// 시간 창은 "HH:mm-HH:mm" 형식. 자정을 가로지르는 창(22:00-06:00)도 지원한다.
|
||||
/// </summary>
|
||||
public sealed class DownloadScheduler
|
||||
{
|
||||
/// <summary>예약 창("HH:mm-HH:mm"). null/빈이면 항상 허용.</summary>
|
||||
public string? Window { get; }
|
||||
|
||||
/// <summary>예약 창이 설정돼 있으면 true.</summary>
|
||||
public bool Enabled => !string.IsNullOrWhiteSpace(Window);
|
||||
|
||||
public DownloadScheduler(string? window = null)
|
||||
{
|
||||
Window = string.IsNullOrWhiteSpace(window) ? null : window.Trim();
|
||||
}
|
||||
|
||||
/// <summary>지정 시각에 다운로드가 허용되는지.</summary>
|
||||
public bool CanRunNow(DateTime now)
|
||||
{
|
||||
if (!Enabled || TryParse(Window!, out var start, out var end) == false) return true;
|
||||
var t = now.TimeOfDay;
|
||||
return start <= end
|
||||
? t >= start && t < end
|
||||
: t >= start || t < end; // 자정 가로지름(예: 22:00-06:00)
|
||||
}
|
||||
|
||||
/// <summary>다음 예약 창 시작까지 남은 시간. 항상 허용이면 Zero.</summary>
|
||||
public TimeSpan TimeUntilNextWindow(DateTime now)
|
||||
{
|
||||
if (!Enabled || !TryParse(Window!, out var start, out _)) return TimeSpan.Zero;
|
||||
var target = now.Date + start;
|
||||
if (target <= now) target = target.AddDays(1);
|
||||
return target - now;
|
||||
}
|
||||
|
||||
/// <summary>"HH:mm-HH:mm" 파싱. 형식이 틀리면 false(게이트는 열린 채 둔다).</summary>
|
||||
public static bool TryParse(string window, out TimeSpan start, out TimeSpan end)
|
||||
{
|
||||
start = end = TimeSpan.Zero;
|
||||
var parts = (window ?? "").Split('-');
|
||||
if (parts.Length != 2) return false;
|
||||
return TryParseTime(parts[0], out start) && TryParseTime(parts[1], out end);
|
||||
}
|
||||
|
||||
private static bool TryParseTime(string s, out TimeSpan ts)
|
||||
{
|
||||
ts = TimeSpan.Zero;
|
||||
var p = s.Trim().Split(':');
|
||||
if (p.Length != 2 || !int.TryParse(p[0], out var h) || !int.TryParse(p[1], out var m)) return false;
|
||||
if (h is < 0 or > 23 || m is < 0 or > 59) return false;
|
||||
ts = new TimeSpan(h, m, 0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue