WIP 229 폴더 충돌 (WebDAV): 합칠 때 안쪽의 받는 쪽이 더 새 파일 건너뜀 · 이동은 건너뛴 원본 남김 (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>묶음6
parent
62e8d52031
commit
a4a5c4289a
|
|
@ -760,16 +760,24 @@ class FileOps {
|
|||
final total = await _totalSizeV(sources);
|
||||
var done = 0;
|
||||
final made = <String>[];
|
||||
Future<void> copyAny(String src, String target) async {
|
||||
Future<void> copyAny(String src, String target, {bool inner = false}) async {
|
||||
_check();
|
||||
final st = await vStat(src);
|
||||
if (st == null) throw FileSystemException(tr('원본이 없습니다'), src);
|
||||
if (st.isDir) {
|
||||
await vMkdirs(target);
|
||||
for (final e in await vList(src)) {
|
||||
await copyAny(e.path, vJoin(target, vBasename(e.path)));
|
||||
await copyAny(e.path, vJoin(target, vBasename(e.path)), inner: true);
|
||||
}
|
||||
} else {
|
||||
// 229: 합치는 폴더 안에서 받는 쪽이 더 새것이면 예전 것으로 덮지 않는다 (WebDAV 도 로컬과 같게)
|
||||
if (inner && skipNewerInMerge) {
|
||||
final t = await vStat(target);
|
||||
if (t != null && !t.isDir && t.modified.isAfter(st.modified.add(const Duration(seconds: 2)))) {
|
||||
keptNewer.add(src);
|
||||
return;
|
||||
}
|
||||
}
|
||||
await _copyFileV(src, target, st.size, st.modified, (n) {
|
||||
done += n;
|
||||
onProgress?.call(src, done, total);
|
||||
|
|
@ -806,12 +814,35 @@ class FileOps {
|
|||
made.add(target);
|
||||
continue;
|
||||
}
|
||||
final keptBefore = keptNewer.length;
|
||||
made.addAll(await _copyV([s], destDir, onProgress: onProgress));
|
||||
final kept = keptNewer.sublist(keptBefore);
|
||||
if (kept.isEmpty) {
|
||||
await vDelete(s);
|
||||
} else {
|
||||
// 229: 건너뛴 (받는 쪽이 더 새) 원본은 남기고 옮긴 것만 지운다
|
||||
await _deleteExceptV(s, kept.toSet());
|
||||
}
|
||||
}
|
||||
return made;
|
||||
}
|
||||
|
||||
/// 229: [dir] (로컬 · WebDAV) 안에서 [keep] 의 파일만 남기고 지운다 (비게 된 폴더도)
|
||||
Future<bool> _deleteExceptV(String dir, Set<String> keep) async {
|
||||
var empty = true;
|
||||
for (final e in await vList(dir)) {
|
||||
if (e.isDir) {
|
||||
if (!await _deleteExceptV(e.path, keep)) empty = false;
|
||||
} else if (keep.contains(e.path)) {
|
||||
empty = false;
|
||||
} else {
|
||||
await vDelete(e.path);
|
||||
}
|
||||
}
|
||||
if (empty) await vDelete(dir);
|
||||
return empty;
|
||||
}
|
||||
|
||||
Future<int> _mirrorV(String srcDir, String dstDir, {bool delete = false, bool update = false, bool strict = false}) async {
|
||||
var n = 0;
|
||||
Future<void> walk(String src, String dst) async {
|
||||
|
|
|
|||
|
|
@ -281,6 +281,27 @@ void main() {
|
|||
expect(File(p.join(src, 'a.txt')).existsSync(), isFalse);
|
||||
});
|
||||
|
||||
test('229 폴더 충돌 (WebDAV 로): 폴더를 덮어쓰기로 합쳐도 안쪽의 받는 쪽이 더 새 파일은 건너뛰고 원본에 남긴다', () async {
|
||||
HttpOverrides.global = null;
|
||||
final d = p.join(tmp.path, 's3', 'D');
|
||||
final oldSame = File(p.join(d, 'same.txt'))
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('old');
|
||||
oldSame.setLastModifiedSync(DateTime(2026, 1, 1));
|
||||
File(p.join(d, 'other.txt')).writeAsStringSync('o');
|
||||
File(p.join(remote.path, 'D', 'same.txt'))
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('NEWER');
|
||||
final job = TransferJob(sources: [d], dest: 'dav://nas/', move: true, conflict: NameConflict.overwrite, skipNewer: true);
|
||||
await job.run();
|
||||
expect(job.error, isNull);
|
||||
expect(File(p.join(remote.path, 'D', 'same.txt')).readAsStringSync(), 'NEWER', reason: '더 새 파일 그대로');
|
||||
expect(File(p.join(remote.path, 'D', 'other.txt')).readAsStringSync(), 'o');
|
||||
expect(oldSame.existsSync(), isTrue, reason: '건너뛴 원본은 이동이어도 남김');
|
||||
expect(File(p.join(d, 'other.txt')).existsSync(), isFalse, reason: '옮긴 것은 지움');
|
||||
expect(job.leftInSource, 1);
|
||||
});
|
||||
|
||||
test('214 d (WebDAV 로): 로컬 원본의 링크는 따라가지 않는다', () async {
|
||||
HttpOverrides.global = null;
|
||||
final outside = Directory(p.join(tmp.path, 'outside2'))..createSync();
|
||||
|
|
|
|||
Loading…
Reference in New Issue