JJ_MKVMaker/lib/startup/windows_quit.dart

121 lines
5.1 KiB
Dart

/// 약속: Windows 의 종료 요청. [WindowsQuit.call] 이 true 를 돌려주면 프로그램을 끝낸다 (정리는 이미 마침).
/// - 창 ✕ · 종료 버튼: 설정 "종료를 누르면" 에 따라 백그라운드로 (창만 숨김) / 묻지 않고 종료 / 고르기
/// - 트레이 "종료" · 업데이트 설치 ([force]): 항상 종료 (받는 중인 다운로드 · 복사가 있으면 확인)
/// 끝낼 때는 작업 중지 → 다운로드 정리 → Tor 끄기 → 중복 실행 정리 → 작업이 멈출 때까지 → 목록 · 창 위치 저장 차례로.
library;
import 'package:flutter/material.dart';
import '../app/app_controller.dart';
import '../app/copy_center.dart';
import '../app/download_manager.dart';
import '../app/tor_service.dart';
import '../l10n/tr.dart';
import '../platform/windows/desktop_shell.dart';
import '../platform/windows/exit_trace.dart';
import '../platform/windows/session_marker.dart';
import '../platform/windows/single_instance.dart';
import '../platform/windows/window_memory.dart';
import '../ui/exit_dialog.dart';
import '../ui/exit_guard.dart' show confirmStopCopies;
import '../ui/downloads_page.dart' show confirmExit;
import 'startup_helpers.dart';
class WindowsQuit {
final AppController controller;
final DownloadManager downloads;
final GlobalKey<NavigatorState> navigatorKey;
final SingleInstance instance;
final WindowMemory memory;
final SessionMarker session;
WindowsQuit({
required this.controller,
required this.downloads,
required this.navigatorKey,
required this.instance,
required this.memory,
required this.session,
});
Future<bool> call({bool force = false}) async {
final shell = controller.services.shell;
final desktop = shell is DesktopShell ? shell : null;
var mode = force || (desktop?.closeFromTray ?? false) ? 'force' : controller.settings.run.closeAction;
// 트레이 아이콘이 없으면 숨긴 창을 끝낼 방법이 없다 → 고르게 한다
if (mode == 'background' && !(desktop?.hasTray ?? false)) mode = 'ask';
if (mode == 'ask') {
final ctx = navigatorKey.currentContext;
if (ctx == null || !ctx.mounted) return false;
final n = downloads.activeCount;
final copies = CopyCenter.peekOf(controller)?.activeCount ?? 0;
final running = [
if (controller.currentJob != null) controller.currentJob!,
if (n > 0) trf('다운로드 {0}개', [n]),
if (copies > 0) trf('복사 · 이동 {0}개', [copies]),
].join(' · ');
final pick = await showCloseChoice(ctx,
running: running.isEmpty ? tr('진행 중인 작업 없음') : running, hotkey: controller.settings.run.showHotkey);
if (pick == null) return false;
if (pick.$2) controller.updateSettings((x) => x.run.closeAction = pick.$1);
mode = pick.$1;
}
if (mode == 'background') {
await shell.hide();
return false;
}
if (mode == 'force') {
final ctx = navigatorKey.currentContext;
if (ctx != null && ctx.mounted && !await confirmExit(ctx, downloads)) return false;
}
// 43: 복사 · 이동 중이면 (설정이 "묻지 않고 종료" 여도) 한 번 묻는다
{
final ctx = navigatorKey.currentContext;
if (ctx != null && ctx.mounted && !await confirmStopCopies(ctx, controller)) return false;
}
ExitTrace.start();
final job = controller.currentJob;
final wait = controller.pendingJobs.length;
final steps = [
ExitStep(job == null ? tr('작업 확인 (진행 중인 작업 없음)') : trf('작업 중지: {0}{1}', [job, wait > 0 ? trf(' · 대기 {0}개', [wait]) : '']),
() async => controller.cancel()),
ExitStep(tr('다운로드 정리 (yt-dlp · aria2 종료)'), downloads.shutdown),
ExitStep(tr('Tor 끄기'), () => TorService.instance.stop()),
// 228: 다음 실행이 이 창에 넘기려 하지 않게 (instance.json 지우기 · 잠금 파일 놓기)
ExitStep(tr('중복 실행 막기 정리'), instance.close),
ExitStep(tr('쓰레드 확인 (AI · 변환 작업이 멈출 때까지)'), () async {
while (controller.busy) {
await Future<void>.delayed(const Duration(milliseconds: 100));
}
}),
ExitStep(tr('동영상 목록 · 창 위치 저장'), () async {
controller.stopVideoListShare();
await memory.save();
controller.note(tr('── 정상 종료 ──'));
session.markClean();
}),
ExitStep(tr('창 종료'), () async {}),
];
final ctx2 = navigatorKey.currentContext;
if (ctx2 != null && ctx2.mounted) {
await showExitProgress(ctx2, lastWork: lastWorkText(controller), steps: [
for (final s in steps)
ExitStep(s.label, () async {
try {
await s.run();
} finally {
ExitTrace.mark(trf('끝: {0}', [s.label]));
}
}),
]);
ExitTrace.mark(tr('종료 창 닫힘'));
} else {
for (final s in steps) {
try {
await s.run().timeout(const Duration(seconds: 8));
} catch (_) {}
}
}
return true;
}
}