From ba332f9f840be993634d9185de4cfcd4022e6e4d Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Fri, 9 Oct 2026 20:52:02 +0900 Subject: [PATCH] =?UTF-8?q?E10=20=C2=B7=2050=20=EB=B3=B4=EC=B6=A9:=20?= =?UTF-8?q?=EB=A1=9C=EC=BB=AC=20=ED=8F=B4=EB=8D=94=EB=A5=BC=20=EC=9D=BD?= =?UTF-8?q?=EC=A7=80=20=EB=AA=BB=ED=95=98=EB=A9=B4=20=EB=B9=88=20=ED=8F=B4?= =?UTF-8?q?=EB=8D=94=EB=A1=9C=20=EB=B3=B4=EC=9D=B4=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EA=B2=8C=20("=EC=9D=BD=EC=9D=84=20=EC=88=98=20=EC=97=86?= =?UTF-8?q?=EC=8A=B5=EB=8B=88=EB=8B=A4=20=C2=B7=20=EC=9D=B4=EC=9C=A0"=20?= =?UTF-8?q?=EC=99=80=20[=EB=8B=A4=EC=8B=9C=20=EC=8B=9C=EB=8F=84])?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - listEntries 가 Directory.list 의 오류를 버려 권한 없는 폴더 · 빠진 USB · 없는 폴더가 빈 폴더로 보였다. 하나도 읽지 못했으면 그 오류를 던지고, 일부를 읽은 뒤의 오류는 읽은 것까지 보여 준다 - 부르는 다른 곳: [폴더 재생] 은 이유 알림, [MKV 목록에 추가] 는 읽지 못한 폴더를 건너뛰고 알림, 그림 보기는 고른 그림만 - 시험: 빈 폴더 · 없는 폴더 · C:\System Volume Information (권한 없음) - 탐색기에 "이 폴더를 읽을 권한이 없습니다" 와 [다시 시도] Co-Authored-By: Claude Opus 5.5 --- assets/l10n/en.json | 2 ++ assets/l10n/ja.json | 2 ++ assets/l10n/zh-Hans.json | 2 ++ lib/core/file_ops.dart | 6 +++++- lib/ui/explorer_page.dart | 26 ++++++++++++++++++++++---- test/explorer_conflict_test.dart | 14 ++++++++++++++ test/file_ops_test.dart | 9 +++++++++ 7 files changed, 56 insertions(+), 5 deletions(-) diff --git a/assets/l10n/en.json b/assets/l10n/en.json index 5a50363..38a9598 100644 --- a/assets/l10n/en.json +++ b/assets/l10n/en.json @@ -1400,6 +1400,8 @@ "일시정지": "Pause", "읽기 전용 매체입니다": "Read-only media", "읽을 수 없습니다": "Cannot read", + "읽을 수 없습니다: {0}": "Cannot read: {0}", + "읽을 수 없어 건너뛴 폴더: {0}": "Skipped folders that could not be read: {0}", "읽을 수 없음": "Cannot read", "읽지 못한 원래 파일은 지우지 않고 아래에 보관했습니다:": "The original file that could not be read was not deleted; it is kept here:", "읽지 못한 폴더 {0}개 (권한 · 연결 문제) - 그 안은 찾지 못했습니다": "{0} unreadable folders (permission or connection problem) - not searched inside", diff --git a/assets/l10n/ja.json b/assets/l10n/ja.json index 0e8ecf1..885a2b6 100644 --- a/assets/l10n/ja.json +++ b/assets/l10n/ja.json @@ -1400,6 +1400,8 @@ "일시정지": "一時停止", "읽기 전용 매체입니다": "読み取り専用メディアです", "읽을 수 없습니다": "読み込めません", + "읽을 수 없습니다: {0}": "読み込めません: {0}", + "읽을 수 없어 건너뛴 폴더: {0}": "読み込めずスキップしたフォルダー: {0}", "읽을 수 없음": "読み取れません", "읽지 못한 원래 파일은 지우지 않고 아래에 보관했습니다:": "読めなかった元のファイルは削除せず、ここに保管しました:", "읽지 못한 폴더 {0}개 (권한 · 연결 문제) - 그 안은 찾지 못했습니다": "読めなかったフォルダー {0} 個 (権限・接続の問題) - その中は探せませんでした", diff --git a/assets/l10n/zh-Hans.json b/assets/l10n/zh-Hans.json index 5c21d0d..30142d6 100644 --- a/assets/l10n/zh-Hans.json +++ b/assets/l10n/zh-Hans.json @@ -1400,6 +1400,8 @@ "일시정지": "暂停", "읽기 전용 매체입니다": "只读介质", "읽을 수 없습니다": "无法读取", + "읽을 수 없습니다: {0}": "无法读取: {0}", + "읽을 수 없어 건너뛴 폴더: {0}": "无法读取而跳过的文件夹: {0}", "읽을 수 없음": "无法读取", "읽지 못한 원래 파일은 지우지 않고 아래에 보관했습니다:": "无法读取的原文件没有删除,保存在这里:", "읽지 못한 폴더 {0}개 (권한 · 연결 문제) - 그 안은 찾지 못했습니다": "{0} 个无法读取的文件夹 (权限 · 连接问题) - 未在其中查找", diff --git a/lib/core/file_ops.dart b/lib/core/file_ops.dart index 95108ac..cde28de 100644 --- a/lib/core/file_ops.dart +++ b/lib/core/file_ops.dart @@ -57,7 +57,10 @@ Future> listEntries(String dir, {bool showHidden = false}) async ]; } final out = []; - await for (final e in Directory(dir).list(followLinks: false).handleError((_) {})) { + // E10: 폴더를 읽지 못하면 (권한 없음 · 빠진 USB 등) 빈 폴더로 보이지 않게 던진다 (화면이 "읽을 수 없습니다: 이유" 와 [다시 시도]). + // 일부를 읽은 뒤의 오류는 읽은 것까지 보여 준다 + Object? failed; + await for (final e in Directory(dir).list(followLinks: false).handleError((Object err) => failed ??= err)) { try { final st = await e.stat(); final isDir = st.type == FileSystemEntityType.directory; @@ -66,6 +69,7 @@ Future> listEntries(String dir, {bool showHidden = false}) async out.add(entry); } catch (_) {} } + if (failed != null && out.isEmpty) throw failed!; return out; } diff --git a/lib/ui/explorer_page.dart b/lib/ui/explorer_page.dart index 9ea2072..a381b9a 100644 --- a/lib/ui/explorer_page.dart +++ b/lib/ui/explorer_page.dart @@ -909,7 +909,13 @@ class _ExplorerPageState extends State { /// 그림 보기: 같은 폴더의 그림을 지금 정렬 순서대로 넘겨 본다 Future _viewImages(String path, _Pane? pane) async { final dir = vDirname(path); - final list = pane?.cache[dir] ?? _sorted(await listEntries(dir, showHidden: c.settings.explorerShowHidden)); + // 폴더를 읽지 못하면 (E10: 이제 오류로 알림) 고른 그림 하나만 + List list; + try { + list = pane?.cache[dir] ?? _sorted(await listEntries(dir, showHidden: c.settings.explorerShowHidden)); + } catch (_) { + list = const []; + } final images = [ for (final e in list) if (!e.isDir && c.settings.imageExts.contains(extOf(e.path))) e.path, @@ -1252,7 +1258,13 @@ class _ExplorerPageState extends State { } Future _playFolder(String dir) async { - final list = (await listEntries(dir)).where((e) => !e.isDir && isVideoFile(e.path)).map((e) => e.path).toList(); + final List list; + try { + list = (await listEntries(dir)).where((e) => !e.isDir && isVideoFile(e.path)).map((e) => e.path).toList(); + } catch (e) { + _snack(trf('읽을 수 없습니다: {0}', [explainFileError('$e', dav: isDav(dir)).$1])); + return; + } if (list.isEmpty) { _snack(tr('이 폴더에 동영상이 없습니다.')); return; @@ -1267,15 +1279,21 @@ class _ExplorerPageState extends State { return; } final videos = []; + final unreadable = []; for (final x in paths) { if (vIsDirSync(x)) { - videos.addAll((await listEntries(x)).where((e) => !e.isDir && isVideoFile(e.path)).map((e) => e.path)); + try { + videos.addAll((await listEntries(x)).where((e) => !e.isDir && isVideoFile(e.path)).map((e) => e.path)); + } catch (_) { + unreadable.add(vBasename(x)); // 읽지 못한 폴더는 건너뛰고 알린다 + } } else if (isVideoFile(x)) { videos.add(x); } } + if (unreadable.isNotEmpty) _snack(trf('읽을 수 없어 건너뛴 폴더: {0}', [unreadable.take(3).join(', ')])); if (videos.isEmpty) { - _snack(tr('추가할 동영상이 없습니다.')); + if (unreadable.isEmpty) _snack(tr('추가할 동영상이 없습니다.')); return; } await c.addVideos(videos, allowOutputFolder: true); diff --git a/test/explorer_conflict_test.dart b/test/explorer_conflict_test.dart index e85b5a2..fa26d8a 100644 --- a/test/explorer_conflict_test.dart +++ b/test/explorer_conflict_test.dart @@ -243,6 +243,20 @@ void main() { expect(find.text('앞 화면'), findsOneWidget); }); + testWidgets('E10: 권한 없는 폴더는 빈 폴더가 아니라 "읽을 수 없음 · 이유" 와 [다시 시도]', (tester) async { + const locked = r'C:\System Volume Information'; + if (!Platform.isWindows || !Directory(locked).existsSync()) return; + c.settings.explorerPaths = [locked, right]; + ExplorerPage.debugVolumes = () => [(r'C:\', 'C:'), (p.rootPrefix(tmp.path), p.rootPrefix(tmp.path).substring(0, 2))]; + tester.view.physicalSize = const Size(1600, 900); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.reset); + await tester.runAsync(() => tester.pumpWidget(MaterialApp(home: ExplorerPage(c: c)))); + await settle(tester, () => find.textContaining('이 폴더를 읽을 권한이 없습니다').evaluate().isNotEmpty); + expect(find.textContaining('이 폴더를 읽을 권한이 없습니다'), findsOneWidget); + expect(find.text('다시 시도'), findsOneWidget); + }); + testWidgets('48: 건너뛰기를 고르면 그대로 두고 건너뛴 것을 알린다', (tester) async { await startCopy(tester); await tester.tap(find.text('건너뛰기')); diff --git a/test/file_ops_test.dart b/test/file_ops_test.dart index ff90fe0..31e4c28 100644 --- a/test/file_ops_test.dart +++ b/test/file_ops_test.dart @@ -200,4 +200,13 @@ void main() { expect(File(p.join(b.path, 'k')).readAsStringSync(), 'file'); }); }); + + test('E10: 폴더를 읽지 못하면 빈 목록이 아니라 오류 (권한 없음 · 없는 폴더) · 빈 폴더는 그대로 빈 목록', () async { + final empty = Directory(p.join(tmp.path, 'empty'))..createSync(); + expect(await listEntries(empty.path), isEmpty); + await expectLater(listEntries(p.join(tmp.path, 'gone')), throwsA(isA())); + if (Platform.isWindows && Directory(r'C:\System Volume Information').existsSync()) { + await expectLater(listEntries(r'C:\System Volume Information'), throwsA(isA())); + } + }); }