141 lines
5.6 KiB
Dart
141 lines
5.6 KiB
Dart
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:path/path.dart' as p;
|
|
|
|
import 'app_paths.dart';
|
|
|
|
/// 내장 Chrome (CEF) 실행 파일: 환경 설정에서 "내장 Chrome" 을 고른 사람만 내려받는다.
|
|
///
|
|
/// 공식 배포처의 *Minimal Distribution* 을 받아 `Release\*` (libcef.dll 등) 와 `Resources\*` (pak · locales)
|
|
/// 를 `<프로그램 폴더>\cef` 에 푼다. 실행 파일은 시작할 때 이 폴더에 libcef.dll 이 있으면 Chrome 엔진을 준비한다
|
|
/// (windows/runner/main.cpp) → 내려받은 뒤에는 프로그램을 다시 시작해야 쓸 수 있다.
|
|
class CefRuntime {
|
|
/// packages/webview_cef/third/download.cmake 의 CEF_VERSION 과 같아야 한다 (빌드에 쓴 것과 같은 버전)
|
|
static const version = '149.0.4+g2f1bfd8+chromium-149.0.7827.156';
|
|
static const _cdn = 'https://cef-builds.spotifycdn.com';
|
|
|
|
/// 내려받을 크기 (안내용, 실제 크기는 받을 때 서버가 알려 준 값)
|
|
static const approxDownloadMb = 155;
|
|
|
|
static String get dir => dirOverride ?? p.join(AppPaths.exeDir, 'cef');
|
|
|
|
/// 시험용: 설치 위치 바꾸기
|
|
static String? dirOverride;
|
|
|
|
static bool get installed => File(p.join(dir, 'libcef.dll')).existsSync();
|
|
|
|
/// 이번 실행이 시작할 때 Chrome 엔진이 있었는지 (실행 파일이 그때 준비했는지).
|
|
/// 실행 중에 내려받은 경우는 false → 다시 시작해야 한다. 시작할 때 [rememberStartState] 로 기록.
|
|
static bool readyThisRun = false;
|
|
static void rememberStartState() => readyThisRun = installed;
|
|
|
|
static Uri get downloadUri {
|
|
final name = 'cef_binary_${version}_windows64_minimal.tar.bz2';
|
|
return Uri.parse('$_cdn/${Uri.encodeComponent(name)}');
|
|
}
|
|
|
|
/// 내려받아 설치. [onProgress] 는 0~1 (받기 0~0.85, 풀기 0.85~1). [isCancelled] 가 true 면 멈춘다.
|
|
static Future<void> install({
|
|
required void Function(double progress, String stage) onProgress,
|
|
bool Function()? isCancelled,
|
|
HttpClient? client,
|
|
}) async {
|
|
final work = await Directory.systemTemp.createTemp('jj_cef_');
|
|
final http = client ?? (HttpClient()..userAgent = 'JJ_MKVMaker');
|
|
try {
|
|
// 1. 받기
|
|
final archive = File(p.join(work.path, 'cef.tar.bz2'));
|
|
final req = await http.getUrl(downloadUri);
|
|
final res = await req.close();
|
|
if (res.statusCode != 200) throw HttpException('Chrome 엔진을 받을 수 없습니다 (HTTP ${res.statusCode})');
|
|
final total = res.contentLength;
|
|
var got = 0;
|
|
final sink = archive.openWrite();
|
|
try {
|
|
await for (final chunk in res) {
|
|
if (isCancelled?.call() ?? false) throw const _Cancelled();
|
|
sink.add(chunk);
|
|
got += chunk.length;
|
|
if (total > 0) onProgress(0.85 * got / total, '받는 중');
|
|
}
|
|
} finally {
|
|
await sink.close();
|
|
}
|
|
|
|
// 2. 풀기 (Windows 기본 tar 가 bzip2 를 푼다)
|
|
onProgress(0.86, '푸는 중');
|
|
final out = Directory(p.join(work.path, 'x'))..createSync();
|
|
final r = await Process.run('tar', ['-xf', archive.path, '-C', out.path]);
|
|
if (r.exitCode != 0) throw ProcessException('tar', [], '압축을 풀 수 없습니다: ${r.stderr}', r.exitCode);
|
|
await archive.delete();
|
|
final root = out.listSync().whereType<Directory>().firstOrNull ?? out;
|
|
final release = Directory(p.join(root.path, 'Release'));
|
|
final resources = Directory(p.join(root.path, 'Resources'));
|
|
if (!File(p.join(release.path, 'libcef.dll')).existsSync()) {
|
|
throw const FileSystemException('받은 파일에 libcef.dll 이 없습니다.');
|
|
}
|
|
|
|
// 3. 설치: 새 폴더에 모은 뒤 이름 바꾸기 (중간에 실패해도 반쯤 깔린 폴더가 남지 않게)
|
|
onProgress(0.95, '설치하는 중');
|
|
final staging = Directory('$dir.new');
|
|
if (staging.existsSync()) staging.deleteSync(recursive: true);
|
|
staging.createSync(recursive: true);
|
|
for (final src in [release, resources]) {
|
|
if (!src.existsSync()) continue;
|
|
for (final e in src.listSync(recursive: true)) {
|
|
final rel = p.relative(e.path, from: src.path);
|
|
final lower = e.path.toLowerCase();
|
|
if (lower.endsWith('.lib') || lower.endsWith('.pdb')) continue; // 빌드용 파일
|
|
final to = p.join(staging.path, rel);
|
|
if (e is Directory) {
|
|
Directory(to).createSync(recursive: true);
|
|
} else if (e is File) {
|
|
Directory(p.dirname(to)).createSync(recursive: true);
|
|
e.copySync(to);
|
|
}
|
|
}
|
|
}
|
|
final old = Directory(dir);
|
|
if (old.existsSync()) old.deleteSync(recursive: true);
|
|
staging.renameSync(dir);
|
|
onProgress(1, '완료');
|
|
} on _Cancelled {
|
|
throw const FileSystemException('취소했습니다.');
|
|
} finally {
|
|
if (client == null) http.close(force: true);
|
|
try {
|
|
work.deleteSync(recursive: true);
|
|
} catch (_) {}
|
|
}
|
|
}
|
|
|
|
/// 내려받은 Chrome 엔진 지우기 (다시 시작한 뒤에 지울 수 있다 - 쓰는 중에는 잠겨 있음)
|
|
static Future<bool> uninstall() async {
|
|
try {
|
|
final d = Directory(dir);
|
|
if (d.existsSync()) await d.delete(recursive: true);
|
|
return true;
|
|
} catch (_) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 설치된 크기 (MB, 안내용)
|
|
static int installedMb() {
|
|
try {
|
|
var n = 0;
|
|
for (final e in Directory(dir).listSync(recursive: true)) {
|
|
if (e is File) n += e.lengthSync();
|
|
}
|
|
return n ~/ (1024 * 1024);
|
|
} catch (_) {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
class _Cancelled implements Exception {
|
|
const _Cancelled();
|
|
}
|