Compare commits
No commits in common. "기능추가1" and "vcode_check" have entirely different histories.
기능추가1
...
vcode_chec
|
|
@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 2026.10.20+1
|
||||
version: 2026.10.18+1
|
||||
|
||||
environment:
|
||||
sdk: ^3.13.4
|
||||
|
|
|
|||
|
|
@ -22,9 +22,6 @@ import 'package:jj_mkvmaker/services/platform_services.dart';
|
|||
|
||||
/// l (관리자 지시 10/17): FTP · FTPS (dart:io 로 직접). 실제 서버 시험은 tool/ftp_test_server.py (pyftpdlib) -
|
||||
/// 시험용 파이썬 (sftp 시험과 같은 venv) 에 pyftpdlib 가 없으면 건너뛴다
|
||||
/// 시험 서버 비밀번호 - 무작위 주소에 우연히 들어가지 않게 길고 드물게 (관리자 지시 10/20, 28 이 찾음)
|
||||
const ftpPw = 'ftp-Pw-7Rk2xQ9t';
|
||||
|
||||
void main() {
|
||||
final hasPy = File(sftpTestPython).existsSync();
|
||||
|
||||
|
|
@ -79,8 +76,7 @@ void main() {
|
|||
late String caFp;
|
||||
|
||||
Future<void> start(String mode, {bool chain = false}) async {
|
||||
final pr = await Process.start(
|
||||
sftpTestPython, ['tool/ftp_test_server.py', '--root', root.path, '--mode', mode, '--password', ftpPw, if (chain) '--chain']);
|
||||
final pr = await Process.start(sftpTestPython, ['tool/ftp_test_server.py', '--root', root.path, '--mode', mode, if (chain) '--chain']);
|
||||
proc = pr;
|
||||
final lines = pr.stdout.transform(utf8.decoder).transform(const LineSplitter()).asBroadcastStream();
|
||||
pr.stderr.drain<void>();
|
||||
|
|
@ -115,7 +111,7 @@ void main() {
|
|||
}
|
||||
});
|
||||
|
||||
FtpServer srv({String security = 'explicit', String cert = '', String password = ftpPw}) =>
|
||||
FtpServer srv({String security = 'explicit', String cert = '', String password = 'pw'}) =>
|
||||
FtpServer(id: 'f1', name: 'test', host: '127.0.0.1', port: port, user: 'jj', password: password, security: security, certFingerprint: cert);
|
||||
|
||||
Future<Object?> err(Future<void> Function() f) async {
|
||||
|
|
@ -251,55 +247,13 @@ void main() {
|
|||
FtpRegistry.configure([srv(security: 'none')]);
|
||||
final src = p.join(tmp.path, 'local', 'scene_src'), want = p.join(tmp.path, 'local', 'scene_rsync'), got = p.join(root.path, 'scene');
|
||||
final bad = <String>[];
|
||||
// 관리자 지시 (10/20): 가끔 실패해 원인을 보려고, 실패하면 옵션 · 걸린 시간 · 포트 · 예외 · 다른 점과
|
||||
// 보내는 쪽 · 맞추기 전 앱 쪽 · rsync 쪽 · 앱 쪽 목록 (파일은 크기 · 바뀐 시각 ms) 을 한 번에 보인다
|
||||
String listing(String dir) {
|
||||
final root = Directory(dir);
|
||||
if (!root.existsSync()) return ' (없음)';
|
||||
final rows = <String>[];
|
||||
for (final e in root.listSync(recursive: true)) {
|
||||
final rel = p.relative(e.path, from: dir).replaceAll(r'\', '/');
|
||||
if (e is Directory) {
|
||||
rows.add('$rel/');
|
||||
} else {
|
||||
final s = e.statSync();
|
||||
rows.add('$rel ${s.size}B ${s.modified.toIso8601String()}');
|
||||
}
|
||||
}
|
||||
return (rows..sort()).map((r) => ' $r').join('\n');
|
||||
}
|
||||
|
||||
final sw = Stopwatch()..start();
|
||||
for (final opts in sceneOptions) {
|
||||
final t0 = sw.elapsedMilliseconds;
|
||||
writeScene(src, want);
|
||||
final w = await realRsyncTree(rsync, src, want, opts);
|
||||
final wantList = listing(want);
|
||||
writeScene(src, got);
|
||||
final beforeList = listing(got);
|
||||
Object? error;
|
||||
try {
|
||||
await FileOps().mirror(src, 'ftp://f1/scene', delete: optionsDelete(opts), filter: RsyncFilter.parse(opts));
|
||||
} catch (e, st) {
|
||||
error = '$e\n$st';
|
||||
}
|
||||
await FileOps().mirror(src, 'ftp://f1/scene', delete: optionsDelete(opts), filter: RsyncFilter.parse(opts));
|
||||
final d = treeDiff(w, treeOf(got));
|
||||
if (d.isNotEmpty || error != null) {
|
||||
bad.add([
|
||||
'옵션: $opts (${sw.elapsedMilliseconds - t0}ms, 시험 시작부터 ${t0}ms, 포트 $port, ${DateTime.now().toIso8601String()})',
|
||||
if (error != null) ' 예외: $error',
|
||||
' 다른 점:',
|
||||
...d.map((x) => ' $x'),
|
||||
' 보내는 쪽:',
|
||||
listing(src),
|
||||
' 맞추기 전 앱 쪽:',
|
||||
beforeList,
|
||||
' rsync 쪽:',
|
||||
wantList,
|
||||
' 앱 쪽:',
|
||||
listing(got),
|
||||
].join('\n'));
|
||||
}
|
||||
if (d.isNotEmpty) bad.add('$opts\n ${d.join('\n ')}');
|
||||
}
|
||||
expect(bad, isEmpty, reason: bad.join('\n'));
|
||||
}, skip: hasPy ? false : '시험용 파이썬 없음', timeout: const Timeout(Duration(minutes: 5)));
|
||||
|
|
@ -331,10 +285,7 @@ void main() {
|
|||
final bridge = await RemoteStreamBridge.get();
|
||||
final u1 = bridge.url('ftp://f1/영상/1화.mkv'), u2 = bridge.url('ftp://f1/영상/2화.mkv');
|
||||
expect(u1, startsWith('http://127.0.0.1:'));
|
||||
// 관리자 지시 (10/20, 28 이 찾음): 시험 비밀번호가 두 글자 "pw" 라 다리 주소의 무작위 표 (예 …-4pwvUR-…) 에
|
||||
// 우연히 들어가 "주소에 비밀번호 없음" 이 가끔 실패했다 (비밀번호가 샌 것이 아님) - 길고 드문 비밀번호로 보고, 주소의 사용자 정보도 따로
|
||||
expect(u1, isNot(contains(ftpPw)), reason: '주소에 비밀번호 없음');
|
||||
expect(Uri.parse(u1).userInfo, isEmpty, reason: '주소에 사용자 정보 없음');
|
||||
expect(u1, isNot(contains('pw')), reason: '주소에 비밀번호 없음');
|
||||
Future<(int, String)> get(String url, [String? range]) async {
|
||||
final http = HttpClient();
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -16,9 +16,6 @@ import 'support/sftp_server.dart';
|
|||
|
||||
/// sftp_next: SFTP 동영상을 받지 않고 이 기기 안의 다리 (127.0.0.1) 로 스트리밍 - 같은 폴더의 다음 동영상 · 그 자막.
|
||||
/// 실제 서버 시험은 tool/sftp_test_server.py (paramiko) - 파이썬이 없으면 건너뛴다 (sftp_test 와 같음)
|
||||
/// 시험 서버 비밀번호 - 무작위 주소에 우연히 들어가지 않게 길고 드물게 (관리자 지시 10/20, 28 이 찾음)
|
||||
const sftpPw = 'sftp-Pw-3Lm8vZ2w';
|
||||
|
||||
void main() {
|
||||
final hasServer = sftpTestServerAvailable;
|
||||
late Directory tmp;
|
||||
|
|
@ -29,10 +26,10 @@ void main() {
|
|||
if (!hasServer) return;
|
||||
tmp = Directory.systemTemp.createTempSync('jj_sftpnext_');
|
||||
root = Directory(p.join(tmp.path, 'root'))..createSync();
|
||||
final s = await SftpTestServer.start(root: root.path, hostKeyFile: p.join(tmp.path, 'hostkey'), password: sftpPw);
|
||||
final s = await SftpTestServer.start(root: root.path, hostKeyFile: p.join(tmp.path, 'hostkey'));
|
||||
server = s;
|
||||
SftpRegistry.configure([
|
||||
SftpServer(id: 's1', name: 'test', host: '127.0.0.1', port: s.port, user: 'jj', password: sftpPw, hostKeyFingerprint: s.fingerprint),
|
||||
SftpServer(id: 's1', name: 'test', host: '127.0.0.1', port: s.port, user: 'jj', password: 'pw', hostKeyFingerprint: s.fingerprint),
|
||||
]);
|
||||
Directory(p.join(root.path, '영상')).createSync();
|
||||
File(p.join(root.path, '영상', '1화.mkv')).writeAsStringSync('0123456789');
|
||||
|
|
@ -96,10 +93,7 @@ void main() {
|
|||
expect(u1, endsWith('/${Uri.encodeComponent('1화.mkv')}'), reason: 'mpv 가 확장자로 형식을 알게');
|
||||
expect(u1, isNot(equals(u2)));
|
||||
expect(bridge.url('sftp://s1/영상/1화.mkv'), u1, reason: '같은 경로는 같은 주소');
|
||||
// 관리자 지시 (10/20, 28 이 찾음): 시험 비밀번호가 두 글자 "pw" 라 다리 주소의 무작위 표 (예 …-4pwvUR-…) 에
|
||||
// 우연히 들어가 "주소에 비밀번호 없음" 이 가끔 실패했다 (비밀번호가 샌 것이 아님) - 길고 드문 비밀번호로 보고, 주소의 사용자 정보도 따로
|
||||
expect(u1, isNot(contains(sftpPw)), reason: '주소에 비밀번호 없음');
|
||||
expect(Uri.parse(u1).userInfo, isEmpty, reason: '주소에 사용자 정보 없음');
|
||||
expect(u1, isNot(contains('pw')), reason: '주소에 비밀번호 없음');
|
||||
|
||||
var (code, h, body) = await get(u1);
|
||||
expect((code, body), (200, '0123456789'));
|
||||
|
|
|
|||
|
|
@ -124,8 +124,7 @@ void main() {
|
|||
SmbRegistry.configure(const []);
|
||||
});
|
||||
|
||||
// 길고 드문 비밀번호 - 다리 주소의 무작위 표에 우연히 들어가지 않게 (관리자 지시 10/20, 28 이 찾음)
|
||||
const server = SmbServer(id: 'm1', name: 'NAS', host: 'nas', share: 'video', user: 'jj', password: 'smb-Pw-5Qh9kT4r');
|
||||
const server = SmbServer(id: 'm1', name: 'NAS', host: 'nas', share: 'video', user: 'jj', password: 'pw');
|
||||
|
||||
test('smb:// 로 폴더 · 쓰기 (1MB 조각) · 읽기 · 목록 · 이름 바꾸기 (덮어쓰기) · 지우기 · 화면 이름', () async {
|
||||
SmbRegistry.configure([server]);
|
||||
|
|
@ -197,10 +196,10 @@ void main() {
|
|||
final secrets = MemorySecretStore();
|
||||
final store = SettingsStore(p.join(tmp.path, 'settings.json'), secrets);
|
||||
await store.save(AppSettings()..smbServers = [server]);
|
||||
expect(File(p.join(tmp.path, 'settings.json')).readAsStringSync(), isNot(contains('smb-Pw-5Qh9kT4r')));
|
||||
expect(secrets.values['smb.pw.m1'], 'smb-Pw-5Qh9kT4r');
|
||||
expect(File(p.join(tmp.path, 'settings.json')).readAsStringSync(), isNot(contains('"pw"')));
|
||||
expect(secrets.values['smb.pw.m1'], 'pw');
|
||||
final back = (await SettingsStore(p.join(tmp.path, 'settings.json'), secrets).load()).smbServers.single;
|
||||
expect((back.host, back.share, back.user, back.password), ('nas', 'video', 'jj', 'smb-Pw-5Qh9kT4r'));
|
||||
expect((back.host, back.share, back.user, back.password), ('nas', 'video', 'jj', 'pw'));
|
||||
});
|
||||
|
||||
test('경로 입력의 smb:// · \\\\서버\\공유 → 서버 추가 창에 채울 (서버, 공유, 폴더)', () {
|
||||
|
|
@ -330,10 +329,7 @@ void main() {
|
|||
final bridge = await RemoteStreamBridge.get();
|
||||
final u1 = bridge.url('smb://m1/영상/1화.mkv'), u2 = bridge.url('smb://m1/영상/2화.mkv');
|
||||
expect(u1, startsWith('http://127.0.0.1:'));
|
||||
// 관리자 지시 (10/20, 28 이 찾음): 시험 비밀번호가 두 글자 "pw" 라 다리 주소의 무작위 표 (예 …-4pwvUR-…) 에
|
||||
// 우연히 들어가 "주소에 비밀번호 없음" 이 가끔 실패했다 (비밀번호가 샌 것이 아님) - 길고 드문 비밀번호로 보고, 주소의 사용자 정보도 따로
|
||||
expect(u1, isNot(contains(server.password)), reason: '주소에 비밀번호 없음');
|
||||
expect(Uri.parse(u1).userInfo, isEmpty, reason: '주소에 사용자 정보 없음');
|
||||
expect(u1, isNot(contains('pw')), reason: '주소에 비밀번호 없음');
|
||||
Future<(int, String)> get(String url, [String? range]) async {
|
||||
final http = HttpClient();
|
||||
try {
|
||||
|
|
|
|||
Loading…
Reference in New Issue