import '../core/file_ops.dart' show outermostPaths, samePath, transferProblem; import '../core/name_clash.dart'; import '../core/vfs.dart' show vBasename, vDirname, vJoin, vStat; import '../l10n/tr.dart'; /// 약속 (E5b): 복사 · 이동을 시작하기 전 계획 ([planTransfer]) - CopyCenter.start 앞 단계. /// 입력 = 원본들 · 대상 폴더 · 이동인지, 출력 = [TransferPlan] (실제로 보낼 것 · 건너뛸 것 · 무엇을 물을지 · 막는 문제). /// 오류 없음 (막는 문제는 [TransferPlan.problem] 글로). /// - 고른 폴더 안의 것은 한 번만 (바깥 것만). /// - 135: 같은 폴더로 - 이동인데 일부만 이미 그 폴더에 있으면 그것만 건너뛰고 나머지를 옮긴다. /// 모두 이미 그 폴더면 이동은 할 것이 없고 ([TransferPlan.nothingToMove]), 복사는 사본을 만들지 묻는다 ([TransferPlan.askCopyHere]). /// - [findClashes]: 대상에 같은 이름이 있는 원본 (크기 · 바뀐 날 함께, 같은 폴더로 복사는 늘 새 이름이라 뺌). /// - 다시 쓰는 곳: 탐색기 [복사] · [이동] · 끌어 놓기 · 붙여넣기 · 찾기 결과 복사. class TransferPlan { const TransferPlan({ required this.sources, required this.here, required this.skippedHere, required this.nothingToMove, required this.askCopyHere, required this.problem, }); /// 실제로 보낼 것 final List sources; /// 이미 대상 폴더에 있는 원본 final List here; /// 이동에서 이미 그 폴더에 있어 건너뛰는 것 final List skippedHere; final bool nothingToMove; final bool askCopyHere; /// 시작하지 않는 이유 (원본 · 대상이 없음, 폴더를 자기 안으로 등) - 없으면 null final String? problem; } TransferPlan planTransfer(List sources, String dest, {required bool move}) { sources = outermostPaths(sources); final here = [for (final x in sources) if (samePath(vDirname(x), dest)) x]; var skipped = const []; var rest = sources; if (move && here.isNotEmpty && here.length < sources.length) { skipped = here; rest = [for (final x in sources) if (!here.contains(x)) x]; } final all = here.isNotEmpty && skipped.isEmpty && (move || here.length == sources.length); return TransferPlan( sources: rest, here: here, skippedHere: skipped, nothingToMove: all && move, askCopyHere: all && !move, problem: all && move ? null : transferProblem(rest, dest, move: move), ); } /// 48 · 172: 대상 폴더에 같은 이름이 있는 원본 - 두 파일의 크기 · 바뀐 날을 함께 (어느 쪽이 새것인지 보고 고르게) Future> findClashes(List sources, String dest) async { final out = []; for (final s in sources) { if (samePath(vDirname(s), dest)) continue; try { final there = await vStat(vJoin(dest, vBasename(s))); if (there == null) continue; final here = await vStat(s); out.add((src: s, here: here, there: there)); } catch (_) {} } return out; } /// 154: 항목 이름 (앞의 3개 + "외 n개") String namesOf(List paths) { final names = [for (final x in paths.take(3)) vBasename(x)].join(', '); return paths.length > 3 ? trf('{0} 외 {1}개', [names, paths.length - 3]) : names; }