121 lines
5.1 KiB
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;
|
|
}
|
|
}
|