robocopy 이동은 원본 안의 링크 · junction 을 따라가지 않음 (/XJ, 데이터)
- 데이터 (버그, 개발 중 Windows 에서 재현): robocopy /E /MOVE 가 junction 을 따라가 원본 바깥 폴더의 파일을 옮기고 지웠다 - 이동에 /XJ - 링크는 원본에 남긴다 (앱이 직접 할 때 214 와 같게). rsync 는 링크를 링크로 옮겨 문제없음 (확인) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>fix_mkv
parent
d86a929ba1
commit
f1206ae1f2
|
|
@ -134,10 +134,12 @@ List<List<String>> robocopyRuns({
|
||||||
/// 이동에 붙이는 robocopy 옵션: /MOVE + 같은 파일도 포함.
|
/// 이동에 붙이는 robocopy 옵션: /MOVE + 같은 파일도 포함.
|
||||||
/// 217: 대상에 같은 파일이 있으면 robocopy 는 건너뛰고 원본에 남긴다 (종료 코드 0 - "옮겼습니다" 인데 남음, Windows 에서 확인).
|
/// 217: 대상에 같은 파일이 있으면 robocopy 는 건너뛰고 원본에 남긴다 (종료 코드 0 - "옮겼습니다" 인데 남음, Windows 에서 확인).
|
||||||
/// 같음 (/IS) 뿐 아니라 속성만 다름 (Tweaked, /IT) · 만든 시각만 다름 (Modified, /IM - 다른 프로그램으로 복사한 같은 파일) 도 넣어 원본에서 지운다.
|
/// 같음 (/IS) 뿐 아니라 속성만 다름 (Tweaked, /IT) · 만든 시각만 다름 (Modified, /IM - 다른 프로그램으로 복사한 같은 파일) 도 넣어 원본에서 지운다.
|
||||||
/// 그래도 남는 것은 이동 뒤 다시 세어 알린다 (216)
|
/// 그래도 남는 것은 이동 뒤 다시 세어 알린다 (216).
|
||||||
|
/// 데이터: 원본 안의 링크 · junction 은 빼고 (/XJ) - robocopy 는 기본으로 junction 을 따라가 /MOVE 가
|
||||||
|
/// 링크가 가리키는 원본 바깥 폴더의 파일까지 옮기고 지웠다 (Windows 에서 확인). 앱이 직접 할 때 (214) 와 같게 링크는 남긴다
|
||||||
List<String> robocopyMoveOptions(List<String> opts) => [
|
List<String> robocopyMoveOptions(List<String> opts) => [
|
||||||
'/MOVE',
|
'/MOVE',
|
||||||
for (final o in const ['/IS', '/IT', '/IM'])
|
for (final o in const ['/IS', '/IT', '/IM', '/XJ'])
|
||||||
if (!opts.any((x) => x.toUpperCase() == o)) o,
|
if (!opts.any((x) => x.toUpperCase() == o)) o,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -186,7 +186,7 @@ bool taskDeletes(CopyTask t) {
|
||||||
/// 104: 지우기가 들어 있는 작업은 어디서 실행하든 (모니터링 [실행] · 옵션 저장 뒤 실행 · 최종 정리) Rsync 화면과 같은 미리 보기를 거친다.
|
/// 104: 지우기가 들어 있는 작업은 어디서 실행하든 (모니터링 [실행] · 옵션 저장 뒤 실행 · 최종 정리) Rsync 화면과 같은 미리 보기를 거친다.
|
||||||
/// 지울 목록이 나온 뒤에만 [실행] 이 켜진다. 지우기가 없으면 묻지 않고 true
|
/// 지울 목록이 나온 뒤에만 [실행] 이 켜진다. 지우기가 없으면 묻지 않고 true
|
||||||
/// 178 · 209: 실제로 실행할 방법 · 옵션 (원본 지우기 옵션은 빼고, 이동일 때만 그 방법의 원본 지우기를 붙인다).
|
/// 178 · 209: 실제로 실행할 방법 · 옵션 (원본 지우기 옵션은 빼고, 이동일 때만 그 방법의 원본 지우기를 붙인다).
|
||||||
/// 238: robocopy 이동은 실제로 붙는 /MOVE /IS /IT /IM 까지 (robocopyRuns 와 같게). [skipNewer] (229): robocopy 의 /XO
|
/// 238: robocopy 이동은 실제로 붙는 /MOVE /IS /IT /IM /XJ 까지 (robocopyRuns 와 같게). [skipNewer] (229): robocopy 의 /XO
|
||||||
String effectiveRunText(String method, String options, {required bool move, bool skipNewer = false}) {
|
String effectiveRunText(String method, String options, {required bool move, bool skipNewer = false}) {
|
||||||
final name = method == 'builtin' ? tr('현재 방식') : method;
|
final name = method == 'builtin' ? tr('현재 방식') : method;
|
||||||
if (method == 'builtin') return name;
|
if (method == 'builtin') return name;
|
||||||
|
|
@ -246,7 +246,9 @@ Future<bool> confirmDeletingRun(BuildContext context, CopyTask t, {bool includeM
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
SyncPreviewView(
|
SyncPreviewView(
|
||||||
compute: () async => [
|
compute: () async => [
|
||||||
for (final s in t.sources) ...await previewSync(s, dstOf(s), toRight: true, delete: deletes, update: update, skipLinks: direct),
|
for (final s in t.sources) ...await previewSync(s, dstOf(s), toRight: true, delete: deletes, update: update,
|
||||||
|
// robocopy 이동도 링크를 빼고 (/XJ) 남긴다
|
||||||
|
skipLinks: direct || (robo && t.move)),
|
||||||
],
|
],
|
||||||
onStart: () => set(() {
|
onStart: () => set(() {
|
||||||
preview = null;
|
preview = null;
|
||||||
|
|
|
||||||
|
|
@ -288,8 +288,8 @@ void main() {
|
||||||
testWidgets('209: 모니터링 카드는 실제로 실행할 것을 이동 / 복사와 함께 보인다 (복사 카드 칸의 원본 지우기 옵션은 쓰지 않는다고 알림)', (tester) async {
|
testWidgets('209: 모니터링 카드는 실제로 실행할 것을 이동 / 복사와 함께 보인다 (복사 카드 칸의 원본 지우기 옵션은 쓰지 않는다고 알림)', (tester) async {
|
||||||
expect(effectiveRunText('rsync', '-avPog', move: true), 'rsync -avPog --remove-source-files');
|
expect(effectiveRunText('rsync', '-avPog', move: true), 'rsync -avPog --remove-source-files');
|
||||||
expect(effectiveRunText('rsync', '-avPog --remove-source-files', move: false), 'rsync -avPog');
|
expect(effectiveRunText('rsync', '-avPog --remove-source-files', move: false), 'rsync -avPog');
|
||||||
expect(effectiveRunText('robocopy', '/E /MOV', move: true), 'robocopy /E /MOVE /IS /IT /IM'); // 238: 실제로 붙는 것까지
|
expect(effectiveRunText('robocopy', '/E /MOV', move: true), 'robocopy /E /MOVE /IS /IT /IM /XJ'); // 238: 실제로 붙는 것까지
|
||||||
expect(effectiveRunText('robocopy', '/E', move: true, skipNewer: true), 'robocopy /E /MOVE /IS /IT /IM /XO'); // 229
|
expect(effectiveRunText('robocopy', '/E', move: true, skipNewer: true), 'robocopy /E /MOVE /IS /IT /IM /XJ /XO'); // 229
|
||||||
final a = p.join(tmp.path, 'ca'), b = p.join(tmp.path, 'cb');
|
final a = p.join(tmp.path, 'ca'), b = p.join(tmp.path, 'cb');
|
||||||
Directory(a).createSync(recursive: true);
|
Directory(a).createSync(recursive: true);
|
||||||
Directory(b).createSync(recursive: true);
|
Directory(b).createSync(recursive: true);
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,21 @@ void main() {
|
||||||
expect(job.leftInSource, 0);
|
expect(job.leftInSource, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('robocopy 이동 (데이터): 원본 안의 junction 을 따라가 원본 바깥 폴더의 파일을 옮기고 지우지 않는다 (/XJ)', () async {
|
||||||
|
if (!Platform.isWindows) return;
|
||||||
|
final outside = Directory(p.join(p.dirname(src), 'outside_${p.basename(src)}'))..createSync();
|
||||||
|
addTearDown(() => outside.existsSync() ? outside.deleteSync(recursive: true) : null);
|
||||||
|
final keep = File(p.join(outside.path, 'keep.txt'))..writeAsStringSync('k');
|
||||||
|
Link(p.join(src, 'linked')).createSync(outside.path); // junction
|
||||||
|
final job = TransferJob(sources: [src], dest: dst, move: true, contents: true, method: CopyMethod.robocopy, options: '/E /COPY:DAT /DCOPY:T /R:2 /W:2');
|
||||||
|
await job.run();
|
||||||
|
expect(job.error, isNull);
|
||||||
|
expect(keep.existsSync(), isTrue, reason: '링크가 가리키는 바깥 파일은 그대로');
|
||||||
|
expect(File(p.join(dst, 'linked', 'keep.txt')).existsSync(), isFalse, reason: '링크 안의 것은 옮기지 않음');
|
||||||
|
expect(Link(p.join(src, 'linked')).existsSync(), isTrue, reason: '링크는 원본에 남김');
|
||||||
|
expect(File(p.join(src, 'a.txt')).existsSync(), isFalse, reason: '나머지는 옮김');
|
||||||
|
});
|
||||||
|
|
||||||
test('235 · 229: robocopy 이동도 받는 쪽이 더 새것이면 건너뜀 (환경 설정 → /XO) · 남은 것은 카드 기록에도 (남은 수 · 옮긴 수)', () async {
|
test('235 · 229: robocopy 이동도 받는 쪽이 더 새것이면 건너뜀 (환경 설정 → /XO) · 남은 것은 카드 기록에도 (남은 수 · 옮긴 수)', () async {
|
||||||
// 옵션 칸에는 /XO 가 없다 - 환경 설정 (skipNewer) 만으로 robocopy 가 건너뛴다
|
// 옵션 칸에는 /XO 가 없다 - 환경 설정 (skipNewer) 만으로 robocopy 가 건너뛴다
|
||||||
final old = File(p.join(src, 'old.txt'))..writeAsStringSync('o');
|
final old = File(p.join(src, 'old.txt'))..writeAsStringSync('o');
|
||||||
|
|
|
||||||
|
|
@ -61,8 +61,8 @@ void main() {
|
||||||
bandwidthKBps: 640,
|
bandwidthKBps: 640,
|
||||||
move: true,
|
move: true,
|
||||||
);
|
);
|
||||||
expect(runs[0], [r'C:\a\movies', p.join(r'E:\dst', 'movies'), '/E', '/R:2', '/IPG:100', '/MOVE', '/IS', '/IT', '/IM']);
|
expect(runs[0], [r'C:\a\movies', p.join(r'E:\dst', 'movies'), '/E', '/R:2', '/IPG:100', '/MOVE', '/IS', '/IT', '/IM', '/XJ']);
|
||||||
expect(runs[1], [r'C:\b', r'E:\dst', '1.mkv', '2.mkv', '/R:2', '/IPG:100', '/MOV', '/IS', '/IT', '/IM']); // 파일만: /E 빼고 /MOV. 217: 이동은 같은 파일도 (/IS · /IT · /IM)
|
expect(runs[1], [r'C:\b', r'E:\dst', '1.mkv', '2.mkv', '/R:2', '/IPG:100', '/MOV', '/IS', '/IT', '/IM', '/XJ']); // 파일만: /E 빼고 /MOV. 217: 이동은 같은 파일도 (/IS · /IT · /IM), 링크는 빼고 (/XJ)
|
||||||
expect(runs[2].sublist(0, 3), [r'C:\c', r'E:\dst', '3.mkv']);
|
expect(runs[2].sublist(0, 3), [r'C:\c', r'E:\dst', '3.mkv']);
|
||||||
expect(robocopyIpg(0), 0);
|
expect(robocopyIpg(0), 0);
|
||||||
// 229: 환경 설정 "받는 쪽이 더 새것이면 건너뛰기" → /XO (옵션 칸에 있으면 두 번 넣지 않음)
|
// 229: 환경 설정 "받는 쪽이 더 새것이면 건너뛰기" → /XO (옵션 칸에 있으면 두 번 넣지 않음)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue