WIP 데이터 검토: 대소문자만 다른 이름으로 바꿀 때 대소문자를 가리는 폴더의 다른 파일을 덮던 것 (검토 대기)
관리자 지시 (10/18, cd 의 X-plore e~i · 칸 그림 데이터 안전 검토) 중 찾음: FileOps.rename 은 a.txt → A.txt 처럼 대소문자만 바뀌면 "같은 파일의 대소문자 바꾸기" 로 보고 같은 이름 확인을 건너뛰었다. 대소문자를 가리는 곳 (Android 앱 폴더 · ext4 · 일부 NAS 를 로컬로 붙인 것 · Windows 의 대소문자 구분 폴더) 에 A.txt 가 따로 있으면 File.rename 이 그 파일을 말없이 덮었다 (이름 변경 · 일괄 이름 바꾸기 · [D][T] 모두 이 길). → 대소문자만 다를 때는 폴더 목록에 정확히 그 이름이 따로 있는지 보고, 있으면 "같은 이름이 이미 있습니다" (목록을 못 읽으면 막음). 대소문자를 가리지 않는 보통 Windows 폴더에서 a.txt → A.txt 는 그대로 된다. 시험: file_ops_test (새 1: 보통 폴더는 바뀜 · fsutil 로 만든 대소문자 구분 폴더에서는 막고 두 파일 그대로 - 고치기 전에는 A.txt 를 덮어 실패) · batch_rename · batch_rename_date · app_trash · xplore_tools 34 통과, analyze 문제 없음. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>review_fix v2026.10.12_976
parent
25e3123aac
commit
50bd2bf259
|
|
@ -728,7 +728,7 @@ class FileOps {
|
|||
final target = p.join(p.dirname(path), name);
|
||||
if (target == path) return path;
|
||||
if (FileSystemEntity.typeSync(target) != FileSystemEntityType.notFound &&
|
||||
target.toLowerCase() != path.toLowerCase()) {
|
||||
(target.toLowerCase() != path.toLowerCase() || _otherCaseExists(path, name))) {
|
||||
throw FileSystemException(tr('같은 이름이 이미 있습니다'), target);
|
||||
}
|
||||
final t = FileSystemEntity.typeSync(path);
|
||||
|
|
@ -737,6 +737,17 @@ class FileOps {
|
|||
: (await File(path).rename(target)).path;
|
||||
}
|
||||
|
||||
/// 데이터 검토 (10/18): 대소문자만 바꾸는 이름 (a.jpg → A.jpg) 인데 대소문자를 가리는 저장소 (Android 의 앱 폴더 · ext4 · 일부 NAS) 에
|
||||
/// 그 이름의 다른 파일이 따로 있으면 true - rename 이 그 파일을 말없이 덮지 않게 (가리지 않는 곳이면 목록에 정확히 그 이름이 없다)
|
||||
static bool _otherCaseExists(String path, String name) {
|
||||
if (p.basename(path) == name) return false;
|
||||
try {
|
||||
return Directory(p.dirname(path)).listSync(followLinks: false).any((e) => p.basename(e.path) == name);
|
||||
} catch (_) {
|
||||
return true; // 모르면 막는다 (덮는 것보다 낫다)
|
||||
}
|
||||
}
|
||||
|
||||
static Future<String> makeFolder(String dir, String name) async {
|
||||
final n = name.trim();
|
||||
final bad = nameProblem(n, windowsRules: usesWindowsNames(dir));
|
||||
|
|
|
|||
|
|
@ -54,6 +54,29 @@ void main() {
|
|||
throwsA(isA<FileSystemException>()));
|
||||
});
|
||||
|
||||
test('데이터 검토: 대소문자만 바꾸는 이름 - 대소문자를 가리는 폴더에 그 이름의 다른 파일이 있으면 덮지 않는다', () async {
|
||||
// 보통 폴더 (Windows - 가리지 않음): a.txt → A.txt 는 바뀐다
|
||||
final plain = Directory(p.join(tmp.path, 'plain'))..createSync();
|
||||
final a = File(p.join(plain.path, 'a.txt'))..writeAsStringSync('lower');
|
||||
final up = await FileOps.rename(a.path, 'A.txt');
|
||||
expect(p.basename(up), 'A.txt');
|
||||
expect([for (final e in plain.listSync()) p.basename(e.path)], ['A.txt']);
|
||||
expect(File(up).readAsStringSync(), 'lower');
|
||||
// 대소문자를 가리는 폴더 (Android 앱 폴더 · ext4 와 같음 - Windows 는 fsutil 로)
|
||||
if (!Platform.isWindows) return;
|
||||
final cs = Directory(p.join(tmp.path, 'cs'))..createSync();
|
||||
final r = await Process.run('fsutil.exe', ['file', 'setCaseSensitiveInfo', cs.path, 'enable']);
|
||||
if (r.exitCode != 0) {
|
||||
markTestSkipped('대소문자를 가리는 폴더를 만들 수 없음: ${r.stdout}');
|
||||
return;
|
||||
}
|
||||
final lower = File(p.join(cs.path, 'a.txt'))..writeAsStringSync('lower');
|
||||
final upper = File(p.join(cs.path, 'A.txt'))..writeAsStringSync('UPPER');
|
||||
await expectLater(FileOps.rename(lower.path, 'A.txt'), throwsA(isA<FileSystemException>()));
|
||||
expect(lower.readAsStringSync(), 'lower');
|
||||
expect(upper.readAsStringSync(), 'UPPER', reason: '다른 파일을 덮지 않는다');
|
||||
});
|
||||
|
||||
test('이동 · 삭제 · 이름 바꾸기 · 새 폴더', () async {
|
||||
final f = file('a/x.mkv', 'video');
|
||||
final b = Directory(p.join(tmp.path, 'b'))..createSync();
|
||||
|
|
|
|||
Loading…
Reference in New Issue