Files
spotx/Services/InstallerService.cs
T
2026-09-14 20:24:13 +02:00

160 lines
7.4 KiB
C#
Raw Permalink 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.
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<int> InstallAsync(InstallOptions o,
IProgress<(int Percent, string Status)> progress,
IProgress<string> log, CancellationToken ct = default)
=> RunAsync(o, isUninstall: false, progress, log, ct);
public Task<int> UninstallAsync(
IProgress<(int Percent, string Status)> progress,
IProgress<string> log, CancellationToken ct = default)
=> RunAsync(new InstallOptions(false, false, false, false),
isUninstall: true, progress, log, ct);
private async Task<int> RunAsync(InstallOptions o, bool isUninstall,
IProgress<(int, string)> progress, IProgress<string> 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<string>();
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<int> ExecAsync(string file, string[] argv,
Action<string> onLine, CancellationToken ct)
{
var tcs = new TaskCompletionSource<int>(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;
}
}