102 lines
4.3 KiB
Dart
102 lines
4.3 KiB
Dart
import 'package:flutter/widgets.dart';
|
|
|
|
import '../../app/copy_center.dart';
|
|
import '../../app/rsync_runner.dart';
|
|
import '../../core/file_ops.dart' show FileOpCancelled;
|
|
import '../../core/vfs.dart' show vDirname, vMissingSync;
|
|
import '../../l10n/tr.dart';
|
|
import 'explorer_controller.dart';
|
|
import 'explorer_jobs.dart';
|
|
import 'rsync_run_dialog.dart';
|
|
import 'transfer_flow.dart';
|
|
|
|
/// 약속 (E5d-5): Rsync 화면의 [→ ← ⇄] - 두 창에서 고른 폴더로 rsync. 막는 조건 · 옵션 · 미리 보기 · 실행은 [RsyncRun],
|
|
/// 확인 창은 rsync_run_dialog, 진행은 [ExplorerJobs], rsync 실행 파일은 [ExplorerTransfers.ensureRsync].
|
|
/// 끝나면 두 폴더를 다시 읽고 결과를 알린다 (Host). 확인 창이 떠 있는 동안 또 누르면 무시
|
|
class ExplorerRsyncFlow {
|
|
ExplorerRsyncFlow(this.ctl, this.jobs, this.transfers);
|
|
|
|
final ExplorerController ctl;
|
|
final ExplorerJobs jobs;
|
|
final ExplorerTransfers transfers;
|
|
|
|
/// Rsync 화면: 고른 왼쪽 · 오른쪽 폴더로 rsync. [right] 왼쪽 → 오른쪽, [left] 오른쪽 → 왼쪽 (둘 다면 함께).
|
|
/// 폴더 "안의 것" 을 맞춘다 (원본/ → 대상/). 함께 할 때는 받는 쪽이 더 새 파일은 건너뛴다 (-u).
|
|
/// 실행한 것은 모니터링에 남는다 (같은 것을 다시 하면 기억한 옵션으로).
|
|
/// 확인 창이 떠 있는 동안 또 누르면 무시 (창이 겹쳐 아래 창을 잘못 누르지 않게 - 110)
|
|
bool _asking = false;
|
|
|
|
Future<void> run(BuildContext context, {required bool right, required bool left}) async {
|
|
if (_asking) return;
|
|
_asking = true;
|
|
try {
|
|
await _run(context, right: right, left: left);
|
|
} finally {
|
|
_asking = false;
|
|
}
|
|
}
|
|
|
|
Future<void> _run(BuildContext context, {required bool right, required bool left}) async {
|
|
final l = ctl.panes[0].sel.marked.firstOrNull, r = ctl.panes[1].sel.marked.firstOrNull;
|
|
// 막는 조건 · 작업 · 옵션 · 미리 보기 · 실행은 RsyncRun (E5c), 확인 창은 rsync_run_dialog
|
|
final (:run, :problem, :missing) = RsyncRun.prepare(CopyCenter.of(ctl.c),
|
|
l: l,
|
|
r: r,
|
|
right: right,
|
|
left: left,
|
|
allowInnerToOuter: ctl.c.settings.allowInnerToOuter,
|
|
jobs: this.jobs.list,
|
|
viaDav: l != null && r != null && ExplorerTransfers.viaDav([l, r]),
|
|
copySkipNewer: ctl.c.settings.copySkipNewer);
|
|
if (run == null) {
|
|
ctl.host.snack(problem!);
|
|
if (missing) {
|
|
for (final x in ctl.panes) {
|
|
x.sel.removeWhere(vMissingSync);
|
|
}
|
|
await ctl.refreshAll([vDirname(l!), vDirname(r!)]);
|
|
}
|
|
return;
|
|
}
|
|
if (!context.mounted) return;
|
|
final go = await showRsyncRunDialog(context, run);
|
|
// 110: 무엇을 골랐는지 작업 기록에 (실행 · 취소 · 닫힘)
|
|
ctl.c.note(trf('Rsync 확인 창 ({0}): {1}', [
|
|
run.both ? '⇄' : right ? '→' : '←',
|
|
go == true ? tr('실행') : go == false ? tr('취소') : tr('닫힘 (실행 안 함)'),
|
|
]));
|
|
if (go != true || !context.mounted) return;
|
|
if (run.nestedDelete()) {
|
|
ctl.host.snack(RsyncRun.nestedDeleteMessage);
|
|
return;
|
|
}
|
|
final exe = run.viaDav ? '' : await transfers.ensureRsync(context);
|
|
if (exe == null || !context.mounted) return;
|
|
final res = await run.execute(
|
|
rsyncExe: exe,
|
|
alive: () => context.mounted,
|
|
note: ctl.c.note,
|
|
onStarted: this.jobs.start);
|
|
// 205: 시작하지 못하면 이유를 알린다
|
|
if (res.notStarted != null && context.mounted) ctl.host.snack(trf('시작하지 못했습니다: {0}', [res.notStarted]));
|
|
final jobs = res.jobs;
|
|
if (jobs.isEmpty || !context.mounted) return;
|
|
// 원본 폴더까지 지웠으면 고른 표시도 뺀다
|
|
for (final x in ctl.panes) {
|
|
x.sel.removeWhere(vMissingSync);
|
|
}
|
|
await ctl.refreshAll([run.l, run.r, vDirname(run.l), vDirname(run.r)]);
|
|
for (final x in ctl.panes) {
|
|
x.changed();
|
|
}
|
|
final error = jobs.map((j) => j.error).whereType<Object>().firstOrNull;
|
|
ctl.host.snack(error is FileOpCancelled
|
|
? tr('취소했습니다.')
|
|
: error != null
|
|
? trf('끝나지 못했습니다: {0}', [error])
|
|
// 212 · 216: 이동인지 · 몇 개인지 · 원본에 남은 것
|
|
: jobs.map((j) => j.doneSummary()).join('\n'));
|
|
await this.jobs.dropLater(jobs);
|
|
}
|
|
}
|