Files
cup_edit/examples/flutter/animations/lib/utils/binary_manager.dart
2025-08-25 15:09:07 +08:00

406 lines
12 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:convert';
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
class BinaryManager {
/// 当前应用版本(用于初始化检查)
static String? _currentVersion;
/// 获取当前应用版本
static Future<String> _getCurrentVersion() async {
if (_currentVersion == null) {
final packageInfo = await PackageInfo.fromPlatform();
_currentVersion = '${packageInfo.version}+${packageInfo.buildNumber}';
}
return _currentVersion!;
}
/// 检查是否已完成初始化(基于版本号)
static Future<bool> isInitialized() async {
final supportDir = await getApplicationSupportDirectory();
final currentVersion = await _getCurrentVersion();
final initMarker = File(
p.join(supportDir.path, '.initialized_$currentVersion'),
);
return await initMarker.exists();
}
/// 标记初始化完成(文件名包含版本号)
static Future<void> markInitialized() async {
final supportDir = await getApplicationSupportDirectory();
final currentVersion = await _getCurrentVersion();
final initMarker = File(
p.join(supportDir.path, '.initialized_$currentVersion'),
);
await initMarker.writeAsString('${DateTime.now().toIso8601String()}\n');
}
/// 从assets目录提取文件到指定路径
static Future<void> _extractAsset(String assetPath, String localPath) async {
final localFile = File(localPath);
try {
// 从assets中读取数据
final ByteData data = await rootBundle.load(assetPath);
final List<int> bytes = data.buffer.asUint8List();
// 确保目录存在
final dir = Directory(p.dirname(localPath));
if (!await dir.exists()) {
await dir.create(recursive: true);
}
// 写入本地文件
await localFile.writeAsBytes(bytes);
print('成功提取文件: $assetPath -> $localPath');
} catch (e) {
print('提取资源文件失败 $assetPath: $e');
rethrow;
}
}
/// 初始化应用程序资源 - 将所有必要的assets迁移到系统文件夹
static Future<void> initializeAppResources() async {
final currentVersion = await _getCurrentVersion();
if (await isInitialized()) {
print('应用程序资源已初始化,当前版本: $currentVersion');
return;
}
try {
// 迁移shell脚本
await _migrateShellScript();
// 迁移二进制文件
await _migrateBinaries();
// 迁移模型文件
await _migrateModels();
// 迁移3D模型文件
await migrate3DModels();
// 标记初始化完成
await markInitialized();
print('应用程序资源初始化完成');
} catch (e) {
print('应用程序资源初始化失败: $e');
rethrow;
}
}
/// 迁移3D模型文件
static Future<void> migrate3DModels() async {
print('开始迁移3D模型文件...');
final supportDir = await getApplicationSupportDirectory();
final models3dDir = Directory(p.join(supportDir.path, 'cup'));
// 创建3D模型目录
if (!await models3dDir.exists()) {
await models3dDir.create(recursive: true);
}
// 3D模型文件列表
final model3dFiles = ['model.gltf', 'model.bin', 'texture.jpg'];
// 迁移所有3D模型文件
for (final modelFile in model3dFiles) {
try {
// if (modelFile == 'model.gltf') {
// // 对于GLTF文件我们需要修改内容
// await _extractAndModifyGltfAsset(
// 'assets/cup/$modelFile',
// p.join(models3dDir.path, modelFile),
// );
// } else {
// 对于其他文件,直接复制
await _extractAsset(
'assets/cup/$modelFile',
p.join(models3dDir.path, modelFile),
);
// }
} catch (e) {
print('迁移3D模型文件失败 $modelFile: $e');
// 继续迁移其他模型文件
}
}
print('3D模型文件迁移完成');
}
static Future<void> _extractAndModifyGltfAsset(
String assetPath, String targetPath) async {
try {
// 读取资源文件内容
final byteData = await rootBundle.load(assetPath);
final buffer = byteData.buffer;
final gltfContent = utf8.decode(
buffer.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes));
// 修改纹理路径(去掉"./"前缀)
final modifiedGltfContent =
gltfContent.replaceAll('"./texture.jpg"', '"texture.jpg"');
// 写入修改后的内容到目标文件
final file = File(targetPath);
await file.writeAsBytes(utf8.encode(modifiedGltfContent));
print('已修改并迁移: $assetPath -> $targetPath');
} catch (e) {
print('提取并修改GLTF资源失败: $e');
rethrow;
}
}
static Future<void> _migrateShellScript() async {
print('开始迁移脚本文件...');
final supportDir = await getApplicationSupportDirectory();
// 迁移 Shell 脚本 (macOS/Linux)
final shellScriptPath = p.join(supportDir.path, 'upscale_to_8k_single.sh');
await _extractAsset('assets/upscale_to_8k_single.sh', shellScriptPath);
// 设置脚本执行权限(仅限 macOS/Linux
if (Platform.isMacOS || Platform.isLinux) {
try {
final result = await Process.run('chmod', ['+x', shellScriptPath]);
if (result.exitCode == 0) {
print('成功设置Shell脚本执行权限: $shellScriptPath');
} else {
print('设置Shell脚本执行权限失败: ${result.stderr}');
}
} catch (e) {
print('设置Shell脚本权限异常: $e');
}
}
print('脚本文件迁移完成');
}
/// 迁移二进制文件
static Future<void> _migrateBinaries() async {
print('开始迁移二进制文件...');
final supportDir = await getApplicationSupportDirectory();
final binDir = Directory(p.join(supportDir.path, 'bin'));
// 创建bin目录
if (!await binDir.exists()) {
await binDir.create(recursive: true);
}
// macOS 二进制文件
final macBinDir = Directory(p.join(binDir.path, 'macos'));
if (!await macBinDir.exists()) {
await macBinDir.create(recursive: true);
}
await _extractAsset(
'assets/bin/mac/upscayl-bin',
p.join(macBinDir.path, 'upscayl-bin'),
);
// Windows 二进制文件
final winBinDir = Directory(p.join(binDir.path, 'windows'));
if (!await winBinDir.exists()) {
await winBinDir.create(recursive: true);
}
await _extractAsset(
'assets/bin/windows/upscayl-bin.exe',
p.join(winBinDir.path, 'upscayl-bin.exe'),
);
await _extractAsset(
'assets/bin/windows/vcomp140.dll',
p.join(winBinDir.path, 'vcomp140.dll'),
);
await _extractAsset(
'assets/bin/windows/vcomp140d.dll',
p.join(winBinDir.path, 'vcomp140d.dll'),
);
// 设置 macOS/Linux 二进制文件执行权限
if (Platform.isMacOS || Platform.isLinux) {
final binPath = p.join(macBinDir.path, 'upscayl-bin');
try {
final result = await Process.run('chmod', ['+x', binPath]);
if (result.exitCode == 0) {
print('成功设置执行权限: $binPath');
} else {
print('设置执行权限失败: ${result.stderr}');
}
} catch (e) {
print('设置权限异常: $e');
}
}
print('二进制文件迁移完成');
}
/// 迁移模型文件
static Future<void> _migrateModels() async {
print('开始迁移模型文件...');
final supportDir = await getApplicationSupportDirectory();
final modelsDir = Directory(p.join(supportDir.path, 'models'));
// 创建models目录
if (!await modelsDir.exists()) {
await modelsDir.create(recursive: true);
}
// 模型文件列表
final modelFiles = [
'high-fidelity-4x.bin',
'high-fidelity-4x.param',
'digital-art-4x.bin',
'digital-art-4x.param',
'remacri-4x.bin',
'remacri-4x.param',
'ultrasharp-4x.bin',
'ultrasharp-4x.param',
'upscayl-standard-4x.bin',
'upscayl-standard-4x.param',
'ultramix-balanced-4x.bin',
'ultramix-balanced-4x.param',
'upscayl-lite-4x.bin',
'upscayl-lite-4x.param',
];
// 迁移所有模型文件
for (final modelFile in modelFiles) {
try {
await _extractAsset(
'assets/models/$modelFile',
p.join(modelsDir.path, modelFile),
);
} catch (e) {
print('迁移模型文件失败 $modelFile: $e');
// 继续迁移其他模型文件
}
}
print('模型文件迁移完成');
}
/// 获取模型文件目录路径
static Future<String> getModelsPath() async {
final supportDir = await getApplicationSupportDirectory();
return p.join(supportDir.path, 'cup', "model.gltf");
}
/// 获取3D模型文件目录路径
static Future<String> get3DModelsPath() async {
final supportDir = await getApplicationSupportDirectory();
return p.join(supportDir.path, 'cup');
}
static Future<Map<String, dynamic>> readLocalJson() async {
// 获取应用支持目录(默认在 ~/Library/Application Support/
final appSupportDir = await getApplicationSupportDirectory();
final targetDir = Directory('${appSupportDir.path}/cup');
// 检查目录是否存在,若不存在则创建
if (!await targetDir.exists()) {
await targetDir.create(recursive: true);
}
print('file=${targetDir.path}/model.gltf');
// 构建 JSON 文件路径
final file = File('${targetDir.path}/model.gltf');
if (await file.exists()) {
final content = await file.readAsString();
return jsonDecode(content);
} else {
throw Exception('JSON 文件不存在');
}
}
/// 获取特定的3D模型文件路径
static Future<String> get3DModelPath(String filename) async {
final models3dPath = await get3DModelsPath();
return p.join(models3dPath, filename);
}
/// 获取脚本文件路径 (仅适用于macOS/Linux)
static Future<String> getScriptPath() async {
if (Platform.isWindows) {
throw UnsupportedError('Windows平台已使用专门的UpscaleWindowsService不再需要脚本文件');
}
final supportDir = await getApplicationSupportDirectory();
final scriptName = 'upscale_to_8k_single.sh';
final scriptPath = p.join(supportDir.path, scriptName);
final scriptFile = File(scriptPath);
if (!await scriptFile.exists()) {
throw FileSystemException(
'脚本文件不存在: $scriptPath\n请确保应用程序已正确初始化资源',
scriptPath,
);
}
return scriptPath;
}
/// 获取二进制文件路径
static Future<String> getBinaryPath() async {
final supportDir = await getApplicationSupportDirectory();
String platformDir;
String binaryName;
if (Platform.isMacOS) {
platformDir = 'macos';
binaryName = 'upscayl-bin';
} else if (Platform.isWindows) {
platformDir = 'windows';
binaryName = 'upscayl-bin.exe';
} else if (Platform.isLinux) {
platformDir = 'macos'; // Linux 使用 macOS 的二进制文件
binaryName = 'upscayl-bin';
} else {
throw UnsupportedError('不支持的操作系统: ${Platform.operatingSystem}');
}
final binPath = p.join(supportDir.path, 'bin', platformDir, binaryName);
// 检查文件是否存在
final binFile = File(binPath);
if (!await binFile.exists()) {
throw FileSystemException('二进制文件不存在: $binPath\n请确保应用程序已正确初始化资源', binPath);
}
return binPath;
}
/// 设置脚本执行权限
static Future<void> setScriptExecutable(String scriptPath) async {
// 设置脚本执行权限(仅限 macOS/Linux
if (Platform.isMacOS || Platform.isLinux) {
try {
final result = await Process.run('chmod', ['+x', scriptPath]);
if (result.exitCode == 0) {
print('成功设置脚本执行权限: $scriptPath');
} else {
print('设置脚本执行权限失败: ${result.stderr}');
}
} catch (e) {
print('设置脚本权限异常: $e');
}
}
// Windows 不需要设置执行权限
}
}