From e93b0b0be61ea66d1bb1d878df02c79b1d445514 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Sat, 24 Oct 2026 15:35:07 +0900 Subject: [PATCH] =?UTF-8?q?SecretGate.passIf:=20"=EB=B9=84=EB=B0=80=20?= =?UTF-8?q?=EA=B0=92=EC=9D=B4=20=EC=9E=88=EC=9C=BC=EB=A9=B4=20=EB=AC=B8?= =?UTF-8?q?=EC=9D=84=20=EC=A7=80=EB=82=98=EC=95=BC=20=ED=95=A8"=20?= =?UTF-8?q?=EC=9D=84=20=ED=95=9C=20=EA=B3=B3=EC=97=90=EC=84=9C=20(?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=EC=9E=90=2010/24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SecretGate.passIf(hasSecret, force): 비밀 값이 없으면 묻지 않고 지나감, 있으면 pass 의 답 - 모은 곳 (같은 꼴의 복사 9곳): 원격 등록 requireSecretGate · 플레이어 (WebDAV 머리) · 그림 AI (API 키) · WebDAV · FTP · SFTP · SMB 설정 · AI 서비스 설정 (저장된 비밀 값이 있는 것을 고치기 전). 오류 종류 · 닫기는 그대로 - 그대로 둔 것 (같은 꼴 아님): 사용자가 직접 누른 일 앞에서 다시 묻는 8곳 (탐색기 · 폴더 고르기 · 다시 시도 · 보관함 · 자막 찾기 등), OpenSubtitles (비밀 값 조건 없이 늘 문 - 따로 보고) - 약속 시험 한 줄 (remote_registry_contract: 묻는지 · 답 · force) 사용자가 보게 될 동작: 바뀌는 것 없음 확인: analyze 0 · 단위 1498 ×2 · 통합 6/6 ×2 · base10 214장 0px Co-Authored-By: Claude Opus 5.5 --- lib/core/remote_registry.dart | 2 +- lib/core/secret_gate.dart | 4 ++++ lib/platform/common/media_kit_full_player.dart | 2 +- lib/services/image_ai.dart | 2 +- lib/ui/ai_settings.dart | 2 +- lib/ui/ftp_settings.dart | 2 +- lib/ui/sftp_settings.dart | 2 +- lib/ui/smb_settings.dart | 2 +- lib/ui/webdav_settings.dart | 2 +- test/remote_registry_contract_test.dart | 15 +++++++++++++++ 10 files changed, 27 insertions(+), 8 deletions(-) diff --git a/lib/core/remote_registry.dart b/lib/core/remote_registry.dart index 9df9091..8f4038a 100644 --- a/lib/core/remote_registry.dart +++ b/lib/core/remote_registry.dart @@ -75,5 +75,5 @@ class RemoteRegistry { /// 문: 비밀 값이 있으면 마스터 확인을 지나야 한다 (못 지나면 연결 전에 [DavLockedException]). 비밀 값이 없으면 묻지 않는다 Future requireSecretGate(bool hasSecret) async { - if (hasSecret && !await SecretGate.pass()) throw DavLockedException(tr(secretGateMessage)); + if (!await SecretGate.passIf(hasSecret)) throw DavLockedException(tr(secretGateMessage)); } diff --git a/lib/core/secret_gate.dart b/lib/core/secret_gate.dart index 893e00f..49b463e 100644 --- a/lib/core/secret_gate.dart +++ b/lib/core/secret_gate.dart @@ -6,6 +6,10 @@ class SecretGate { /// 써도 되면 true static Future pass({bool force = false}) => check?.call(force) ?? Future.value(true); + + /// 비밀 값이 있을 때만 문을 지나야 한다 ([hasSecret] 가 false 면 묻지 않고 true). 저장된 비밀 값을 쓰기 · 보이기 · 고치기 전에 부른다. + /// 원격 등록 · 플레이어 · 그림 AI · 서버 · 서비스 설정이 함께 쓴다 (관리자 10/24 - 같은 꼴의 복사를 한 곳으로) + static Future passIf(bool hasSecret, {bool force = false}) => hasSecret ? pass(force: force) : Future.value(true); } /// 마스터 비밀번호를 넣지 않아 막혔을 때의 안내 diff --git a/lib/platform/common/media_kit_full_player.dart b/lib/platform/common/media_kit_full_player.dart index dc7a64f..0125074 100644 --- a/lib/platform/common/media_kit_full_player.dart +++ b/lib/platform/common/media_kit_full_player.dart @@ -148,7 +148,7 @@ class MediaKitFullPlayer implements MediaPlayer { if (dav == null) return mk.Media(f); // 124: 저장된 비밀번호로 여는 것이면 (마스터 비밀번호를 정해 두었으면 묻는다) // 사용자가 직접 튼 것이니 한 번 취소했어도 다시 묻는다 - if (dav.headers.isNotEmpty && !await SecretGate.pass(force: true)) throw Exception(tr(secretGateMessage)); + if (!await SecretGate.passIf(dav.headers.isNotEmpty, force: true)) throw Exception(tr(secretGateMessage)); return mk.Media(dav.url, httpHeaders: dav.headers); } diff --git a/lib/services/image_ai.dart b/lib/services/image_ai.dart index 6e9d268..013b273 100644 --- a/lib/services/image_ai.dart +++ b/lib/services/image_ai.dart @@ -140,7 +140,7 @@ class RemoteImageEngine implements ImageAiEngine { Future> generate(ImageGenRequest r, {required String outDir, required String baseName, AiProgress? onProgress}) async { _cancel = false; // 124: 저장된 API 키를 쓰기 전에 (마스터 비밀번호를 정해 두었으면) - if (service.apiKey.isNotEmpty && !await SecretGate.pass(force: true)) throw ImageAiException(tr(secretGateMessage)); + if (!await SecretGate.passIf(service.apiKey.isNotEmpty, force: true)) throw ImageAiException(tr(secretGateMessage)); final seed = resolveSeed(r.seed); onProgress?.call(null, trf('{0} 에 보내는 중', [service.label])); final List> images = switch (service.kind) { diff --git a/lib/ui/ai_settings.dart b/lib/ui/ai_settings.dart index d661216..7e048e8 100644 --- a/lib/ui/ai_settings.dart +++ b/lib/ui/ai_settings.dart @@ -468,7 +468,7 @@ String _kindName(String kind) => switch (kind) { /// 서비스 추가 · 고치기. 저장된 API 키가 칸에 채워지므로 고칠 때는 먼저 마스터 비밀번호 (127 과 같음) Future editAiService(BuildContext context, AppController c, {AiService? old}) async { - if (old != null && old.apiKey.isNotEmpty && !await SecretGate.pass(force: true)) return; + if (old != null && !await SecretGate.passIf(old.apiKey.isNotEmpty, force: true)) return; if (!context.mounted) return; final name = TextEditingController(text: old?.name ?? ''); final url = TextEditingController(text: old?.url ?? 'http://'); diff --git a/lib/ui/ftp_settings.dart b/lib/ui/ftp_settings.dart index 30aa782..a3a4987 100644 --- a/lib/ui/ftp_settings.dart +++ b/lib/ui/ftp_settings.dart @@ -128,7 +128,7 @@ Future trustFtpCert(AppController c, String id, FtpCertException e) => /// FTP 서버 추가 · 고치기 창. 저장한 서버를 돌려준다 (취소면 null) Future editFtpServer(BuildContext context, AppController c, {FtpServer? old}) async { - if (old != null && old.password.isNotEmpty && !await SecretGate.pass(force: true)) return null; + if (old != null && !await SecretGate.passIf(old.password.isNotEmpty, force: true)) return null; if (!context.mounted) return null; final name = TextEditingController(text: old?.name ?? ''); final host = TextEditingController(text: old?.host ?? ''); diff --git a/lib/ui/sftp_settings.dart b/lib/ui/sftp_settings.dart index d7d8309..3962697 100644 --- a/lib/ui/sftp_settings.dart +++ b/lib/ui/sftp_settings.dart @@ -144,7 +144,7 @@ Future trustSftpHostKey(AppController c, String id, SftpHostKeyException e /// SFTP 서버 추가 · 고치기 창. 저장한 서버를 돌려준다 (취소면 null) Future editSftpServer(BuildContext context, AppController c, {SftpServer? old}) async { // 저장된 비밀번호 · 키가 칸에 채워지므로 먼저 마스터 비밀번호 (정해 두었으면) - if (old != null && (old.password.isNotEmpty || old.privateKey.isNotEmpty) && !await SecretGate.pass(force: true)) return null; + if (old != null && !await SecretGate.passIf(old.password.isNotEmpty || old.privateKey.isNotEmpty, force: true)) return null; if (!context.mounted) return null; final name = TextEditingController(text: old?.name ?? ''); final host = TextEditingController(text: old?.host ?? ''); diff --git a/lib/ui/smb_settings.dart b/lib/ui/smb_settings.dart index 623aa2b..0ae4bf0 100644 --- a/lib/ui/smb_settings.dart +++ b/lib/ui/smb_settings.dart @@ -113,7 +113,7 @@ Future deleteSmbServer(BuildContext context, AppController c, SmbServer s) /// SMB 서버 추가 · 고치기 창. 저장한 서버를 돌려준다 (취소면 null). [from]: 경로 입력에 넣은 smb:// · \\서버\공유 (채워 둠) Future editSmbServer(BuildContext context, AppController c, {SmbServer? old, String? from}) async { - if (old != null && old.password.isNotEmpty && !await SecretGate.pass(force: true)) return null; + if (old != null && !await SecretGate.passIf(old.password.isNotEmpty, force: true)) return null; if (!context.mounted) return null; final pre = from == null ? null : parseSmbAddress(from); final name = TextEditingController(text: old?.name ?? ''); diff --git a/lib/ui/webdav_settings.dart b/lib/ui/webdav_settings.dart index b53f6a1..d346b9d 100644 --- a/lib/ui/webdav_settings.dart +++ b/lib/ui/webdav_settings.dart @@ -99,7 +99,7 @@ List davServerUsers(AppSettings s, String id) { /// WebDAV 서버 추가 · 고치기 창. 저장한 서버를 돌려준다 (취소면 null). Future editDavServer(BuildContext context, AppController c, {DavServer? old}) async { // 127: 저장된 비밀번호가 칸에 채워지므로 (눈 버튼으로 보임) 먼저 마스터 비밀번호 (정해 두었으면) - if (old != null && old.password.isNotEmpty && !await SecretGate.pass(force: true)) return null; + if (old != null && !await SecretGate.passIf(old.password.isNotEmpty, force: true)) return null; if (!context.mounted) return null; final name = TextEditingController(text: old?.name ?? ''); final url = TextEditingController(text: old?.url ?? 'https://'); diff --git a/test/remote_registry_contract_test.dart b/test/remote_registry_contract_test.dart index 46b35d8..c5943f7 100644 --- a/test/remote_registry_contract_test.dart +++ b/test/remote_registry_contract_test.dart @@ -96,4 +96,19 @@ void main() { } expect(asked, 0); }); + + test('SecretGate.passIf: 비밀 값이 있을 때만 묻고 그 답을 따름 · 없으면 묻지 않고 지나감 · force 를 넘김', () async { + final asked = []; + var answer = false; + SecretGate.check = (force) async { + asked.add(force); + return answer; + }; + expect(await SecretGate.passIf(false), isTrue); + expect(asked, isEmpty); + expect(await SecretGate.passIf(true), isFalse); + answer = true; + expect(await SecretGate.passIf(true, force: true), isTrue); + expect(asked, [false, true]); + }); }