M3 ThumbPolicy: 원격 썸네일 "Wi-Fi 에서만" 의 망 판단 (전역 함수 activeMeteredOf) 을 PlatformServices.isMetered 주입으로

- lan_scan.dart: deviceMetered (실제 - Android 만 묻고 PC 는 false, 물을 수 없으면 true) 는 그대로 함수로 두고, 바꿀 수 있는 전역 변수는 뺌
- remoteThumbsAllowedNow 는 c.services.isMetered 를 부름 (처음 값 = deviceMetered)
- 시험: remote_thumbs_test 는 isMetered 를 넘김 (2회 통과)
- 썸네일 캐시 상한 · 받기 허락은 이미 RemoteThumbs 생성자로 넘기는 값이라 그대로. netKindsOf (망 종류) 는 M4 LanFinder 몫
- 사용자가 보게 될 동작: 없음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
modular
RyuJongJin 2026-10-25 17:21:29 +09:00
parent 26d6b01752
commit 39b76e2423
5 changed files with 18 additions and 17 deletions

View File

@ -31,6 +31,7 @@ PlatformServices shotServices() {
clock: r.clock,
vault: r.vault,
privileged: r.privileged,
isMetered: r.isMetered,
updater: r.updater,
usage: r.usage == null ? null : const FixedUsage(),
models: r.models,

View File

@ -41,10 +41,8 @@ Future<Map<String, String>?> _androidNetKinds() async {
}
/// X-plore 비교 5-1 ② (관리자 지시 10/21): 지금 쓰는 망이 데이터 요금제인지 (모바일 데이터 · 요금제 Wi-Fi). Android 만 묻고,
/// 그 밖 (PC) 은 false. 물을 수 없으면 true (받지 않는 쪽으로). 시험에서 바꾼다
Future<bool> Function() activeMeteredOf = _androidMetered;
Future<bool> _androidMetered() async {
/// 그 밖 (PC) 은 false. 물을 수 없으면 true (받지 않는 쪽으로). 앱은 PlatformServices.isMetered 로 부른다 (설계 M3 ThumbPolicy - 시험은 그것을 바꿈)
Future<bool> deviceMetered() async {
if (!Platform.isAndroid) return false;
try {
return await const MethodChannel('jj_mkvmaker/android').invokeMethod<bool>('activeMetered') ?? true;

View File

@ -9,7 +9,7 @@ import '../app/settings.dart';
import '../app/vault_location.dart';
import '../core/shizuku.dart' show PrivilegedFs;
import '../core/download_detect.dart';
import '../core/lan_scan.dart' show LanHost;
import '../core/lan_scan.dart' show LanHost, deviceMetered;
import '../platform/common/nllb_translator.dart';
import '../platform/common/opensubtitles_provider.dart';
import '../platform/common/whisper_recognizer.dart';
@ -88,6 +88,9 @@ class PlatformServices {
/// 없으면 PrivilegedFs.current 를 그대로 둔다 (시험이 건 가짜를 덮지 않게)
final PrivilegedFs? privileged;
/// 지금 쓰는 망이 데이터 요금제인지 (원격 썸네일 "Wi-Fi 에서만" - 설계 M3 ThumbPolicy). 처음 값 = 실제 (Android 만 묻고 PC 는 false)
final Future<bool> Function() isMetered;
/// 이 기기 (Android · Windows · 메모리) - 기기마다 다르게 도는 곳은 이것을 본다 (M1, 처음 값 = 이 기기)
final PlatformInfo platform;
@ -114,8 +117,10 @@ class PlatformServices {
PlatformInfo? platform,
VaultLocation? vault,
this.privileged,
Future<bool> Function()? isMetered,
}) : models = models ?? ModelStore(),
vault = vault ?? VaultLocation.under(storage.tempDirectory),
isMetered = isMetered ?? deviceMetered,
platform = platform ?? PlatformInfo.host,
shell = shell ?? NoopShell();

View File

@ -6,7 +6,6 @@ import 'package:path/path.dart' as p;
import '../app/app_controller.dart';
import '../core/file_ops.dart' show FileEntry;
import '../core/lan_scan.dart' show activeMeteredOf;
import '../core/playlist.dart' show isVideoFile;
import '../core/remote_stream.dart';
import '../core/remote_thumbs.dart';
@ -83,7 +82,7 @@ Future<bool> remoteThumbsAllowedNow(AppController c) async {
}
final now = DateTime.now();
if (_meteredAt == null || now.difference(_meteredAt!) > const Duration(seconds: 10)) {
_metered = await activeMeteredOf();
_metered = await c.services.isMetered();
_meteredAt = now;
}
return !_metered;

View File

@ -5,7 +5,6 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:jj_mkvmaker/app/app_controller.dart';
import 'package:jj_mkvmaker/app/settings.dart';
import 'package:jj_mkvmaker/core/file_ops.dart' show FileEntry;
import 'package:jj_mkvmaker/core/lan_scan.dart' show activeMeteredOf;
import 'package:jj_mkvmaker/core/models.dart';
import 'package:jj_mkvmaker/core/remote_stream.dart';
import 'package:jj_mkvmaker/core/remote_thumbs.dart';
@ -126,24 +125,23 @@ void main() {
});
group('받아도 되는 망 (remoteThumbsAllowedNow)', () {
final was = activeMeteredOf;
tearDown(() {
activeMeteredOf = was;
forgetRemoteThumbNet();
});
AppController ctl() => AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
// 설계 M3 ThumbPolicy: 망 판단은 PlatformServices.isMetered 로 넘긴다 (예전 전역 함수 activeMeteredOf)
var metered = true;
tearDown(forgetRemoteThumbNet);
AppController ctl() => AppController(
PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService(), isMetered: () async => metered));
test('Wi-Fi 에서만 (처음 값): 데이터 요금제 망이면 아니오 · 아니면 예 · 끔 · 늘', () async {
final c = ctl();
activeMeteredOf = () async => true;
metered = true;
forgetRemoteThumbNet();
expect(await remoteThumbsAllowedNow(c), isFalse, reason: '모바일 데이터 (VPN 이어도 그 아래가 모바일 데이터)');
activeMeteredOf = () async => false;
metered = false;
forgetRemoteThumbNet();
expect(await remoteThumbsAllowedNow(c), isTrue, reason: 'Wi-Fi (집 Wi-Fi 위의 Tailscale 포함)');
c.settings.explorer.remoteThumbs = 'off';
expect(await remoteThumbsAllowedNow(c), isFalse);
c.settings.explorer.remoteThumbs = 'always';
activeMeteredOf = () async => true;
metered = true;
forgetRemoteThumbNet();
expect(await remoteThumbsAllowedNow(c), isTrue);
});