using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using SpotXInstaller.Models; namespace SpotXInstaller.Services; public sealed class InstallerService { private const string BashUrl = "https://api.kirieska.xyz/files/SPOTX/spotx.sh"; private const string PsUrl = "https://api.kirieska.xyz/files/SPOTX/run.ps1"; private const string PsUninstallUrl = "https://api.kirieska.xyz/files/SPOTX/uninstall.ps1"; public Task InstallAsync(InstallOptions o, IProgress<(int Percent, string Status)> progress, IProgress log, CancellationToken ct = default) => RunAsync(o, isUninstall: false, progress, log, ct); public Task UninstallAsync( IProgress<(int Percent, string Status)> progress, IProgress log, CancellationToken ct = default) => RunAsync(new InstallOptions(false, false, false, false), isUninstall: true, progress, log, ct); private async Task RunAsync(InstallOptions o, bool isUninstall, IProgress<(int, string)> progress, IProgress log, CancellationToken ct) { bool isWin = OperatingSystem.IsWindows(); string url = isUninstall ? (isWin ? PsUninstallUrl : BashUrl) : (isWin ? PsUrl : BashUrl); string ext = isWin ? ".ps1" : ".sh"; string scriptPath = Path.Combine(Path.GetTempPath(), $"spotx_{Guid.NewGuid():N}{ext}"); try { // === ШАГ 1 (0–20%): скачивание === progress.Report((3, "Загрузка скрипта...")); using (var http = new HttpClient { Timeout = TimeSpan.FromSeconds(60) }) { http.DefaultRequestHeaders.UserAgent.ParseAdd("SpotXInstaller/1.0"); byte[] data = await http.GetByteArrayAsync(url, ct).ConfigureAwait(false); await File.WriteAllBytesAsync(scriptPath, data, ct).ConfigureAwait(false); } progress.Report((20, "Скрипт загружен")); if (!isWin) { progress.Report((25, "Установка прав на исполнение...")); await ExecAsync("/bin/chmod", new[] { "+x", scriptPath }, _ => { }, ct) .ConfigureAwait(false); } progress.Report((30, isUninstall ? "Восстановление..." : "Запуск установки...")); // === ШАГ 2: маппинг флагов === string file; var argv = new List(); if (isWin) { file = "powershell.exe"; argv.AddRange(new[] { "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", scriptPath }); if (isUninstall) { // uninstall.ps1 не принимает флагов — просто отрабатывает } else { if (o.BlockUpdates) argv.Add("-block_update_on"); if (o.HidePodcasts) argv.Add("-podcasts_off"); if (o.HidePremium) argv.Add("-adsections_off"); argv.Add("-no_pause"); // не ждать Enter в конце } } else { file = "/bin/bash"; argv.Add(scriptPath); if (isUninstall) { argv.Add("--uninstall"); } else { if (o.BlockUpdates) argv.Add("-B"); if (o.HidePodcasts) argv.Add("-h"); if (o.HidePremium) argv.Add("-e"); if (o.ClearCache) argv.Add("-c"); } } // === ШАГ 3 (30–95%): запуск + построчный стрим === int cur = 30; var gate = new object(); int exit = await ExecAsync(file, argv.ToArray(), line => { log.Report(line); lock (gate) { cur = Math.Min(cur + 2, 95); progress.Report((cur, isUninstall ? "Восстановление..." : "Установка...")); } }, ct).ConfigureAwait(false); progress.Report((100, "Готово")); return exit; } finally { try { if (File.Exists(scriptPath)) File.Delete(scriptPath); } catch { } } } private static Task ExecAsync(string file, string[] argv, Action onLine, CancellationToken ct) { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var psi = new ProcessStartInfo { FileName = file, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true, }; foreach (var a in argv) psi.ArgumentList.Add(a); var proc = new Process { StartInfo = psi, EnableRaisingEvents = true }; proc.OutputDataReceived += (_, e) => { if (e.Data is { } s) onLine(s); }; proc.ErrorDataReceived += (_, e) => { if (e.Data is { } s) onLine(s); }; proc.Exited += (_, _) => { try { tcs.TrySetResult(proc.ExitCode); } finally { proc.Dispose(); } }; using var reg = ct.Register(() => { try { if (!proc.HasExited) proc.Kill(entireProcessTree: true); } catch { } }); proc.Start(); proc.BeginOutputReadLine(); proc.BeginErrorReadLine(); return tcs.Task; } }