406 lines
12 KiB
Dart
406 lines
12 KiB
Dart
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 不需要设置执行权限
|
||
}
|
||
}
|