extractAsset function

Future<String> extractAsset(
  1. String assetPath, {
  2. String? rename,
  3. bool update = false,
})

从 assets 解压文件 (增强版)

rename 支持两种模式:

  1. 仅文件名 (e.g. "singbox.exe") -> 自动存入 AppData 根目录
  2. 绝对路径 (e.g. "C:\Users...\bin\singbox.exe") -> 存入指定位置

Implementation

Future<String> extractAsset(String assetPath, {String? rename, bool update = false}) async {
  String targetPath;
  // --- 逻辑升级 Start ---
  if (rename != null && p.isAbsolute(rename)) {
    // 模式 A: 传入的是绝对路径,直接信赖
    targetPath = rename;
  } else {
    // 模式 B: 传入的是文件名或相对路径,拼接到 AppData
    Directory appDir = await getApplicationSupportDirectory();
    String fileName = rename ?? p.basename(assetPath);
    targetPath = p.join(appDir.path, fileName);
  }
  File file = File(targetPath);
  // 关键修复:确保父目录存在!(否则 bin/singbox.exe 会报错)
  if (!file.parent.existsSync()) {
    file.parent.createSync(recursive: true);
  }
  // --- 逻辑升级 End ---
  // 检查文件是否存在
  if (!update && await file.exists()) {
    devlog('[extractAsset] ⏭️ 文件已存在,跳过: ${p.basename(targetPath)}');
    if (!Platform.isWindows) {
      await Process.run('chmod', ['+x', targetPath]);
    }
    return targetPath;
  }
  // 读取并写入
  try {
    ByteData data = await rootBundle.load(assetPath);
    await file.writeAsBytes(data.buffer.asUint8List());

    // Linux/Mac 赋予执行权限
    if (!Platform.isWindows) {
      await Process.run('chmod', ['+x', targetPath]);
    }
    devlog('[extractAsset] ✅ 已解压: $assetPath -> $targetPath');
  } catch (e) {
    devlog('[extractAsset] ❌ 解压失败: $e');
    rethrow;
  }
  return targetPath;
}