JJ_MKVMaker/lib/ui/explorer/rsync_run_dialog.dart

152 lines
9.6 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import 'package:flutter/material.dart';
import '../../app/rsync_runner.dart';
import '../../core/sync_preview.dart' show PreviewAction, PreviewItem;
import '../../core/vfs.dart' show vDisplay;
import '../../l10n/tr.dart';
import '../monitor_page.dart' show PruneChoice;
import '../path_label.dart' show shortPath;
import '../sync_preview_view.dart';
import '../theme.dart';
/// Rsync 화면의 실행 확인 창 (E5c) - 실행할 것 · 옵션 · 원본 지우기 · 미리 보기. 규칙은 [RsyncRun] 이 가진다.
/// [실행] 이면 true, [취소] 면 false, 닫으면 null.
Future<bool?> showRsyncRunDialog(BuildContext context, RsyncRun run) {
// 72: 실행 전 비교 결과 (지워질 수를 실행 버튼에)
List<PreviewItem>? preview;
Object? previewError;
// 15 검토 F2: 미리 볼 수 없는 거르기 규칙을 알고 실행하는지 (지우기가 들어 있을 때)
var filterAck = false;
return showDialog<bool>(
context: context,
builder: (ctx) => StatefulBuilder(builder: (ctx, set) => AlertDialog(
scrollable: true,
// 110: 버튼이 좁아 세로로 쌓여도 [취소] 가 먼저 (아래 · 위가 바뀌어 [취소] 자리에 [실행] 이 오지 않게)
actionsOverflowDirection: VerticalDirection.down,
title: Text(run.both ? tr('rsync 양쪽 (⇄)') : 'rsync'),
content: SizedBox(
width: 560,
child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
for (final t in run.tasks) ...[
// 109: 끝 폴더가 보이게 짧게, 전체 경로는 작게
Text(shortPath(t.sources.first), style: const TextStyle(fontWeight: FontWeight.w600)),
Text('→ ${shortPath(t.dest)}', style: const TextStyle(fontWeight: FontWeight.w600)),
Text('${vDisplay(t.sources.first)}/ → ${vDisplay(t.dest)}/',
style: TextStyle(fontSize: 11, color: JjColors.textDim)),
// 111: 이 쌍은 처음 실행 때 기억한 옵션을 쓴다 (환경 설정의 rsync 옵션을 바꿔도 그대로)
if (run.center.tasks.any((x) => x.id == t.id))
Text(tr('이 폴더 쌍은 기억한 옵션을 씁니다 (환경 설정이 아니라 모니터링 > 복사 · rsync 에서 고칩니다)'),
style: TextStyle(fontSize: 11, color: JjColors.textDim)),
Text('rsync ${run.runOpts(run.tasks.indexOf(t))}${run.removeSource ? ' --remove-source-files' : ''}'
'${t.bandwidthKBps > 0 ? ' · ${trf('속도 제한 {0} KB/s', [t.bandwidthKBps])}' : ''}',
style: TextStyle(fontSize: 12, fontFamily: 'monospace', color: JjColors.textDim)),
const SizedBox(height: 10),
],
Text(tr('폴더 안의 것을 맞춥니다 (대상에 같은 이름의 폴더를 새로 만들지 않음).'),
style: TextStyle(fontSize: 12, color: JjColors.textDim)),
// 222: 이동이면 동작대로 (예전에는 이동일 때도 "복사 · 원본 폴더는 그대로" 라고 썼다)
if (run.nested)
Text(
run.removeSource && !run.both
? tr('원본이 대상 폴더 안에 있습니다: 원본의 내용이 대상 폴더 바로 아래로 옮겨집니다 (원본 파일은 지워지고, 원본 폴더는 아래에서 고른 대로).')
: tr('원본이 대상 폴더 안에 있습니다: 원본의 내용이 대상 폴더 바로 아래에 복사됩니다 (원본 폴더는 그대로 남음).'),
style: TextStyle(fontSize: 12, color: JjColors.warning)),
// 1280×720 등 낮은 화면에서도 보이게: 원본 지우기 체크 · 경고는 비교 목록보다 위에 (178)
if (run.both) ...[
Text(keepOptionsTogether(tr('양쪽을 함께: 받는 쪽이 더 새 파일은 덮어쓰지 않습니다 (-u).')),
style: TextStyle(fontSize: 12, color: JjColors.textDim)),
if (run.optionMove)
Text(tr('양쪽 (⇄) 에서는 원본 파일을 지우지 않습니다 (옵션의 --remove-source-files 는 쓰지 않음).'),
style: TextStyle(fontSize: 12, color: JjColors.warning)),
] else ...[
CheckboxListTile(
contentPadding: EdgeInsets.zero,
value: run.removeSource,
onChanged: (v) => set(() {
run.setRemoveSource(v ?? false);
preview = null;
previewError = null;
}),
title: Text(keepOptionsTogether(tr('원본 파일 지우기 (--remove-source-files)'))),
subtitle: Text(tr('대상으로 옮긴 파일을 원본에서 지웁니다 (이동)')),
),
if (run.removeSource) ...[
// 178: 원본이 지워진다는 것을 분명히 (환경 설정 옵션 때문에 켜졌으면 그렇다고)
Text(
run.optionMove
? tr('원본 파일이 지워집니다 (이동). 환경 설정의 rsync 옵션에 --remove-source-files 가 있어 켜 두었습니다 - 끄면 이번에는 원본을 남깁니다.')
: tr('원본 파일이 지워집니다 (이동).'),
style: TextStyle(fontSize: 12, color: JjColors.warning, fontWeight: FontWeight.w600)),
const SizedBox(height: 4),
Text(tr('끝난 뒤 원본의 빈 폴더를 지웁니다 (find 원본/ -type d -empty -delete). 원본 폴더는:'),
style: TextStyle(fontSize: 12, color: JjColors.textDim)),
const SizedBox(height: 6),
PruneChoice(value: run.prune, onChanged: (v) => set(() => run.prune = v)),
],
],
// 72: 무엇이 바뀌는지 파일별로 (→ · ← · 받는 쪽에만 있음 · 지워짐)
const SizedBox(height: 8),
if (run.badOptions().isNotEmpty) OptionProblemNote(run.badOptions()),
// 관리자 지시 (10/20): [실행] 이 막힐 때는 막히는 이유 한 줄만 (미리 볼 수 없음 · [알고 실행합니다] 는 숨김)
if (run.badOptions().isEmpty && run.filterLimits().isNotEmpty)
FilterLimitNote(unsupported: run.filterLimits(), deletes: run.deletes(), ack: filterAck, onAck: (v) => set(() => filterAck = v)),
SyncPreviewView(
// 옵션이 바뀌면 (원본 파일 지우기) 다시 비교
key: ValueKey([for (var i = 0; i < run.tasks.length; i++) run.runOpts(i)].join('|')),
// 177: rsync 로 맞추면 옵션의 거르기 (--exclude 등) 도 비교에 (WebDAV 는 앱이 직접 - 거르기를 따르지 않음)
// 177 · 229: rsync 로 맞추면 옵션의 거르기도 비교에 (WebDAV 등은 앱이 직접)
compute: run.preview,
// 97: 다시 비교하는 동안은 예전 결과로 실행하지 못하게
onStart: () => set(() {
preview = null;
previewError = null;
}),
onResult: (items) => set(() {
preview = items;
previewError = null;
}),
onError: (e) => set(() {
preview = null;
previewError = e;
}),
),
if (run.nestedDelete())
Text(tr('원본이 대상 안에 있으면 지우기 (--delete) 를 함께 쓸 수 없습니다 (대상의 다른 파일이 모두 지워집니다).'),
style: TextStyle(fontSize: 12, color: JjColors.alert, fontWeight: FontWeight.w600)),
// 110: 이 줄은 늘 같은 자리를 차지한다 (글이 사라져 창 높이가 바뀌면 버튼이 움직인다)
SizedBox(
height: 34,
child: run.deletes() && preview == null
? Text(
previewError != null
? tr('지우기가 들어간 실행이라, 비교하지 못하면 실행할 수 없습니다. 원본 · 대상을 확인한 뒤 [다시 비교] 를 누르세요.')
: tr('지우기가 들어간 실행이라, 지울 목록이 나온 뒤에 실행할 수 있습니다.'),
style: TextStyle(fontSize: 12, color: JjColors.warning),
)
: null,
),
const SizedBox(height: 4),
if (run.viaDav)
Text(tr('WebDAV · SFTP · SMB · FTP: rsync 대신 앱이 직접 맞춥니다 (크기 · 바뀐 때 비교, 옵션 중 -u · --delete · --remove-source-files · 거르기 (--exclude · --include · --filter · --delete-excluded) 를 따름).'),
style: TextStyle(fontSize: 12, color: JjColors.accent)),
]),
),
actions: [
TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text(tr('취소'))),
Builder(builder: (_) {
final dels = preview?.where((x) => x.action == PreviewAction.delete).length ?? 0;
return FilledButton(
style: dels > 0 ? FilledButton.styleFrom(backgroundColor: JjColors.danger) : null,
onPressed: run.nestedDelete() || run.badOptions().isNotEmpty || (run.deletes() && preview == null) || (run.deletes() && run.filterLimits().isNotEmpty && !filterAck)
? null
: () => Navigator.pop(ctx, true),
// 110: 글자는 늘 같게 (비교가 끝나 글이 길어지면 버튼 배치가 바뀌어 [취소] 를 누르려다 [실행] 을 누르게 됨).
// 지울 수는 위의 빨간 글 "받는 쪽에서 N개가 지워집니다" 로 보인다
child: Text(run.removeSource ? tr('이동') : tr('실행')),
);
}),
],
)),
);
}