import 'dart:io'; import 'package:crypto/crypto.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import '../../core/sync_tools.dart'; import '../../l10n/tr.dart'; /// Windows 용 rsync: 처음 쓸 때 MSYS2 공식 저장소에서 패키지를 받아 (SHA256 고정 검증) /// 설정 폴더의 rsync\ 에 rsync.exe 와 필요한 DLL 만 풀어 둔다 (앱 업데이트로 지워지지 않게). class RsyncInstaller { /// 시험용으로 바꿀 수 있는 설치 폴더 static String? dirOverride; static Future dir() async => dirOverride ?? p.join((await getApplicationSupportDirectory()).path, 'rsync'); static Future installedPath() async { final f = File(p.join(await dir(), 'rsync.exe')); return await f.exists() ? f.path : null; } /// 내려받기 · 검증 · 풀기. [onProgress] 0~1. 설치된 rsync.exe 경로를 돌려준다. static Future install({void Function(double progress, String file)? onProgress}) async { final target = await dir(); final work = Directory(p.join(Directory.systemTemp.path, 'jj_rsync_${DateTime.now().millisecondsSinceEpoch}')); await work.create(recursive: true); final http = HttpClient()..userAgent = 'JJ_MKVMaker'; try { final total = msys2RsyncPackages.length; for (var i = 0; i < total; i++) { final (name, sha) = msys2RsyncPackages[i]; onProgress?.call(i / total, name); final f = File(p.join(work.path, name)); final req = await http.getUrl(Uri.parse('$msys2Repo$name')); final res = await req.close(); if (res.statusCode != 200) throw HttpException(trf('{0} 을(를) 받지 못했습니다 ({1})', [name, res.statusCode])); await res.pipe(f.openWrite()); final got = (await sha256.bind(f.openRead()).first).toString(); if (got != sha) throw StateError(trf('{0} 의 SHA256 이 다릅니다 (손상되었거나 다른 파일). 설치를 멈췄습니다.', [name])); final t = await Process.run('tar', ['-xf', f.path, '-C', work.path, 'usr/bin']); if (t.exitCode != 0) throw ProcessException('tar', [f.path], '${t.stderr}', t.exitCode); } final bin = Directory(p.join(work.path, 'usr', 'bin')); await Directory(target).create(recursive: true); for (final n in msys2RsyncFiles) { final src = File(p.join(bin.path, n)); if (!await src.exists()) throw StateError(trf('패키지에 {0} 이 없습니다', [n])); await src.copy(p.join(target, n)); } final exe = p.join(target, 'rsync.exe'); final v = await Process.run(exe, ['--version']); if (v.exitCode != 0) throw ProcessException(exe, ['--version'], '${v.stderr}', v.exitCode); onProgress?.call(1, ''); return exe; } finally { http.close(force: true); try { await work.delete(recursive: true); } catch (_) {} } } /// 내려받은 rsync 지우기 static Future uninstall() async { final d = Directory(await dir()); if (await d.exists()) await d.delete(recursive: true); } }