Baseline: WPF/Avalonia/Cli/Core + plans (BROWSER_FEATURES_PLAN v2, MOBILE_PLAN v1)
This commit is contained in:
commit
21b7c4d8bd
60 changed files with 6581 additions and 0 deletions
105
VideoDownloader.Core/Social/DenoRunner.cs
Normal file
105
VideoDownloader.Core/Social/DenoRunner.cs
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
using System.IO.Compression;
|
||||
using System.Runtime.InteropServices;
|
||||
using VideoDownloader.Core.Config;
|
||||
|
||||
namespace VideoDownloader.Core.Social;
|
||||
|
||||
/// <summary>
|
||||
/// Deno(JS 런타임) 해결(설정 오버라이드 → 번들 → PATH → 자동 다운로드) +
|
||||
/// 안전한 다운로드(GitHub Releases zip → deno.exe 추출). yt-dlp 의 EJS(n 챌린지 해석) 실행에 필요.
|
||||
/// YtDlpRunner.RunAsync 가 자식 프로세스 PATH 에 ToolsDir 를 추가하므로, 번들 deno.exe 는 yt-dlp 가 자동 발견.
|
||||
/// </summary>
|
||||
public static class DenoRunner
|
||||
{
|
||||
// yt-dlp 와 동일한 tools 디렉터리를 공유 → PATH 주입 한 번으로 yt-dlp/ffmpeg/deno 모두 발견.
|
||||
public static string ToolsDir => YtDlpRunner.ToolsDir;
|
||||
|
||||
private static string BundledExe => OperatingSystem.IsWindows() ? "deno.exe" : "deno";
|
||||
public static string BundledPath => Path.Combine(ToolsDir, BundledExe);
|
||||
|
||||
private static readonly HttpClient Downloader = new(new HttpClientHandler { AllowAutoRedirect = true })
|
||||
{ Timeout = TimeSpan.FromMinutes(5) };
|
||||
|
||||
/// <summary>Deno 는 Windows ARM 네이티브 빌드가 없어 x64 에뮬레이션. macOS/Linux 는 아키텍처별 에셋.</summary>
|
||||
private static string AssetName
|
||||
{
|
||||
get
|
||||
{
|
||||
bool arm = RuntimeInformation.ProcessArchitecture is Architecture.Arm64;
|
||||
if (OperatingSystem.IsWindows()) return "deno-x86_64-pc-windows-msvc.zip";
|
||||
if (OperatingSystem.IsMacOS()) return arm ? "deno-aarch64-apple-darwin.zip" : "deno-x86_64-apple-darwin.zip";
|
||||
return arm ? "deno-aarch64-unknown-linux-gnu.zip" : "deno-x86_64-unknown-linux-gnu.zip";
|
||||
}
|
||||
}
|
||||
|
||||
private static string ReleaseUrl => $"https://github.com/denoland/deno/releases/latest/download/{AssetName}";
|
||||
|
||||
/// <summary>Deno 경로 해결. 못 찾으면 null.</summary>
|
||||
public static string? Resolve(AppConfig config)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(config.DenoPath) && File.Exists(config.DenoPath))
|
||||
return config.DenoPath;
|
||||
try { if (File.Exists(BundledPath)) return BundledPath; } catch { }
|
||||
return FindOnPath(OperatingSystem.IsWindows() ? "deno.exe" : "deno");
|
||||
}
|
||||
|
||||
/// <summary>Deno 가 없고 AutoInstallDeno 면 번들 디렉터리로 자동 다운로드. 경로 반환(실패 시 null).</summary>
|
||||
public static async Task<string?> EnsureInstalledAsync(AppConfig config, Action<string>? log, CancellationToken ct)
|
||||
{
|
||||
var found = Resolve(config);
|
||||
if (found != null) return found;
|
||||
if (!config.AutoInstallDeno) return null;
|
||||
return await DownloadAsync(config, log, ct);
|
||||
}
|
||||
|
||||
/// <summary>Deno 를 강제로(재)다운로드해 번들 디렉터리에 설치.</summary>
|
||||
public static async Task<string?> DownloadAsync(AppConfig config, Action<string>? log, CancellationToken ct)
|
||||
{
|
||||
var tmpZip = BundledPath + ".zip";
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(ToolsDir);
|
||||
log?.Invoke("Deno 다운로드 중...");
|
||||
using var resp = await Downloader.GetAsync(ReleaseUrl, HttpCompletionOption.ResponseHeadersRead, ct);
|
||||
resp.EnsureSuccessStatusCode();
|
||||
await using (var src = await resp.Content.ReadAsStreamAsync(ct))
|
||||
await using (var dst = new FileStream(tmpZip, FileMode.Create, FileAccess.Write, FileShare.None, 65536, useAsync: true))
|
||||
{
|
||||
await src.CopyToAsync(dst, 65536, ct);
|
||||
await dst.FlushAsync(ct);
|
||||
}
|
||||
|
||||
// zip 에서 deno 실행파일 엔트리만 추출(압축 풀면 deno/deno.exe 단일 파일).
|
||||
try { if (File.Exists(BundledPath)) File.Delete(BundledPath); } catch { }
|
||||
using var archive = new ZipArchive(new FileStream(tmpZip, FileMode.Open, FileAccess.Read, FileShare.None, 65536, useAsync: true), ZipArchiveMode.Read);
|
||||
var entry = archive.Entries.FirstOrDefault(e => e.Name.Equals(BundledExe, StringComparison.OrdinalIgnoreCase));
|
||||
if (entry == null) { log?.Invoke("Deno 추출 실패: zip 에 실행파일이 없음"); return null; }
|
||||
await using var es = entry.Open();
|
||||
await using var fs = new FileStream(BundledPath, FileMode.Create, FileAccess.Write, FileShare.None, 65536, useAsync: true);
|
||||
await es.CopyToAsync(fs, 65536, ct);
|
||||
await fs.FlushAsync(ct);
|
||||
|
||||
log?.Invoke($"Deno 설치 완료: {BundledPath}");
|
||||
return BundledPath;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log?.Invoke($"Deno 다운로드 실패: {e.Message}");
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
try { if (File.Exists(tmpZip)) File.Delete(tmpZip); } catch { }
|
||||
}
|
||||
}
|
||||
|
||||
private static string? FindOnPath(string name)
|
||||
{
|
||||
foreach (var d in (Environment.GetEnvironmentVariable("PATH") ?? "").Split(Path.PathSeparator))
|
||||
{
|
||||
var p = Path.Combine(d.Trim('"'), name);
|
||||
if (File.Exists(p)) return p;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue