Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc91250663 | ||
|
|
45e8e4873a | ||
|
|
30f9e3d1e7 | ||
|
|
3d5fa5ef0f | ||
|
|
263be52245 |
Binary file not shown.
|
After Width: | Height: | Size: 713 B |
+294
-86
@@ -11,149 +11,357 @@ namespace SpotXInstaller.Services;
|
|||||||
|
|
||||||
public sealed class InstallerService
|
public sealed class InstallerService
|
||||||
{
|
{
|
||||||
private const string BashUrl = "https://api.kirieska.xyz/files/SPOTX/spotx.sh";
|
private const string BashUrl =
|
||||||
private const string PsUrl = "https://api.kirieska.xyz/files/SPOTX/run.ps1";
|
"https://api.kirieska.xyz/files/SPOTX/spotx.sh";
|
||||||
private const string PsUninstallUrl = "https://api.kirieska.xyz/files/SPOTX/uninstall.ps1";
|
|
||||||
|
|
||||||
public Task<int> InstallAsync(InstallOptions o,
|
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 options,
|
||||||
IProgress<(int Percent, string Status)> progress,
|
IProgress<(int Percent, string Status)> progress,
|
||||||
IProgress<string> log, CancellationToken ct = default)
|
IProgress<string> log,
|
||||||
=> RunAsync(o, isUninstall: false, progress, log, ct);
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
return RunAsync(
|
||||||
|
options,
|
||||||
|
isUninstall: false,
|
||||||
|
progress,
|
||||||
|
log,
|
||||||
|
ct);
|
||||||
|
}
|
||||||
|
|
||||||
public Task<int> UninstallAsync(
|
public Task<int> UninstallAsync(
|
||||||
IProgress<(int Percent, string Status)> progress,
|
IProgress<(int Percent, string Status)> progress,
|
||||||
IProgress<string> log, CancellationToken ct = default)
|
IProgress<string> log,
|
||||||
=> RunAsync(new InstallOptions(false, false, false, false),
|
CancellationToken ct = default)
|
||||||
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();
|
return RunAsync(
|
||||||
|
new InstallOptions(false, false, false, false),
|
||||||
|
isUninstall: true,
|
||||||
|
progress,
|
||||||
|
log,
|
||||||
|
ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> RunAsync(
|
||||||
|
InstallOptions options,
|
||||||
|
bool isUninstall,
|
||||||
|
IProgress<(int Percent, string Status)> progress,
|
||||||
|
IProgress<string> log,
|
||||||
|
CancellationToken ct)
|
||||||
|
{
|
||||||
|
bool isWindows = OperatingSystem.IsWindows();
|
||||||
|
|
||||||
string url = isUninstall
|
string url = isUninstall
|
||||||
? (isWin ? PsUninstallUrl : BashUrl)
|
? (isWindows ? PsUninstallUrl : BashUrl)
|
||||||
: (isWin ? PsUrl : BashUrl);
|
: (isWindows ? PsUrl : BashUrl);
|
||||||
string ext = isWin ? ".ps1" : ".sh";
|
|
||||||
string scriptPath = Path.Combine(Path.GetTempPath(), $"spotx_{Guid.NewGuid():N}{ext}");
|
string extension = isWindows ? ".ps1" : ".sh";
|
||||||
|
|
||||||
|
string scriptPath = Path.Combine(
|
||||||
|
Path.GetTempPath(),
|
||||||
|
$"spotx_{Guid.NewGuid():N}{extension}");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// === ШАГ 1 (0–20%): скачивание ===
|
ct.ThrowIfCancellationRequested();
|
||||||
progress.Report((3, "Загрузка скрипта..."));
|
|
||||||
using (var http = new HttpClient { Timeout = TimeSpan.FromSeconds(60) })
|
// ---------------------------------------------------------
|
||||||
|
// 1. DOWNLOAD
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
|
progress.Report((5, "Загрузка скрипта..."));
|
||||||
|
|
||||||
|
using (var http = new HttpClient
|
||||||
{
|
{
|
||||||
http.DefaultRequestHeaders.UserAgent.ParseAdd("SpotXInstaller/1.0");
|
Timeout = TimeSpan.FromSeconds(60)
|
||||||
byte[] data = await http.GetByteArrayAsync(url, ct).ConfigureAwait(false);
|
})
|
||||||
await File.WriteAllBytesAsync(scriptPath, data, ct).ConfigureAwait(false);
|
{
|
||||||
|
http.DefaultRequestHeaders.UserAgent.ParseAdd(
|
||||||
|
"SpotXInstaller/1.0");
|
||||||
|
|
||||||
|
byte[] data = await http.GetByteArrayAsync(
|
||||||
|
url,
|
||||||
|
ct);
|
||||||
|
|
||||||
|
await File.WriteAllBytesAsync(
|
||||||
|
scriptPath,
|
||||||
|
data,
|
||||||
|
ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
progress.Report((20, "Скрипт загружен"));
|
progress.Report((20, "Скрипт загружен"));
|
||||||
|
|
||||||
if (!isWin)
|
ct.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// 2. UNIX PERMISSIONS
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
|
if (!isWindows)
|
||||||
{
|
{
|
||||||
progress.Report((25, "Установка прав на исполнение..."));
|
progress.Report(
|
||||||
await ExecAsync("/bin/chmod", new[] { "+x", scriptPath }, _ => { }, ct)
|
(24, "Подготовка скрипта..."));
|
||||||
.ConfigureAwait(false);
|
|
||||||
|
int chmodExit = await ExecAsync(
|
||||||
|
"/bin/chmod",
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
"+x",
|
||||||
|
scriptPath
|
||||||
|
},
|
||||||
|
_ => { },
|
||||||
|
ct);
|
||||||
|
|
||||||
|
if (chmodExit != 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Не удалось установить права на скрипт. Код: {chmodExit}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
progress.Report((30, isUninstall ? "Восстановление..." : "Запуск установки..."));
|
|
||||||
|
|
||||||
// === ШАГ 2: маппинг флагов ===
|
ct.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// 3. BUILD COMMAND
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
string file;
|
string file;
|
||||||
var argv = new List<string>();
|
var arguments = new List<string>();
|
||||||
|
|
||||||
if (isWin)
|
if (isWindows)
|
||||||
{
|
{
|
||||||
file = "powershell.exe";
|
file = "powershell.exe";
|
||||||
argv.AddRange(new[]
|
|
||||||
{
|
|
||||||
"-NoProfile", "-ExecutionPolicy", "Bypass",
|
|
||||||
"-File", scriptPath
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isUninstall)
|
arguments.Add("-NoProfile");
|
||||||
|
arguments.Add("-ExecutionPolicy");
|
||||||
|
arguments.Add("Bypass");
|
||||||
|
arguments.Add("-File");
|
||||||
|
arguments.Add(scriptPath);
|
||||||
|
|
||||||
|
if (!isUninstall)
|
||||||
{
|
{
|
||||||
// uninstall.ps1 не принимает флагов — просто отрабатывает
|
// IMPORTANT:
|
||||||
}
|
// SpotX asks interactively about these options when
|
||||||
|
// neither switch is supplied. Since our GUI has no
|
||||||
|
// interactive PowerShell input, always provide an
|
||||||
|
// explicit answer.
|
||||||
|
|
||||||
|
if (options.BlockUpdates)
|
||||||
|
arguments.Add("-block_update_on");
|
||||||
else
|
else
|
||||||
{
|
arguments.Add("-block_update_off");
|
||||||
if (o.BlockUpdates) argv.Add("-block_update_on");
|
|
||||||
if (o.HidePodcasts) argv.Add("-podcasts_off");
|
if (options.HidePodcasts)
|
||||||
if (o.HidePremium) argv.Add("-adsections_off");
|
arguments.Add("-podcasts_off");
|
||||||
argv.Add("-no_pause"); // не ждать Enter в конце
|
else
|
||||||
|
arguments.Add("-podcasts_on");
|
||||||
|
|
||||||
|
if (options.HidePremium)
|
||||||
|
arguments.Add("-adsections_off");
|
||||||
|
|
||||||
|
arguments.Add("-no_pause");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
file = "/bin/bash";
|
file = "/bin/bash";
|
||||||
argv.Add(scriptPath);
|
|
||||||
|
arguments.Add(scriptPath);
|
||||||
|
|
||||||
if (isUninstall)
|
if (isUninstall)
|
||||||
{
|
{
|
||||||
argv.Add("--uninstall");
|
arguments.Add("--uninstall");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (o.BlockUpdates) argv.Add("-B");
|
if (options.BlockUpdates)
|
||||||
if (o.HidePodcasts) argv.Add("-h");
|
arguments.Add("-B");
|
||||||
if (o.HidePremium) argv.Add("-e");
|
|
||||||
if (o.ClearCache) argv.Add("-c");
|
if (options.HidePodcasts)
|
||||||
|
arguments.Add("-h");
|
||||||
|
|
||||||
|
if (options.HidePremium)
|
||||||
|
arguments.Add("-e");
|
||||||
|
|
||||||
|
if (options.ClearCache)
|
||||||
|
arguments.Add("-c");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// === ШАГ 3 (30–95%): запуск + построчный стрим ===
|
// ---------------------------------------------------------
|
||||||
int cur = 30;
|
// 4. RUN
|
||||||
var gate = new object();
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
int exit = await ExecAsync(file, argv.ToArray(), line =>
|
progress.Report((
|
||||||
|
30,
|
||||||
|
isUninstall
|
||||||
|
? "Запуск восстановления..."
|
||||||
|
: "Запуск установки..."));
|
||||||
|
|
||||||
|
int exitCode = await ExecAsync(
|
||||||
|
file,
|
||||||
|
arguments.ToArray(),
|
||||||
|
line =>
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(line))
|
||||||
|
return;
|
||||||
|
|
||||||
log.Report(line);
|
log.Report(line);
|
||||||
lock (gate)
|
|
||||||
{
|
|
||||||
cur = Math.Min(cur + 2, 95);
|
|
||||||
progress.Report((cur, isUninstall ? "Восстановление..." : "Установка..."));
|
|
||||||
}
|
|
||||||
}, ct).ConfigureAwait(false);
|
|
||||||
|
|
||||||
progress.Report((100, "Готово"));
|
string lower = line.ToLowerInvariant();
|
||||||
return exit;
|
|
||||||
|
if (lower.Contains("download") ||
|
||||||
|
lower.Contains("скач"))
|
||||||
|
{
|
||||||
|
progress.Report(
|
||||||
|
(40, "Загрузка Spotify..."));
|
||||||
|
}
|
||||||
|
else if (lower.Contains("install") ||
|
||||||
|
lower.Contains("установ"))
|
||||||
|
{
|
||||||
|
progress.Report(
|
||||||
|
(60, "Установка Spotify..."));
|
||||||
|
}
|
||||||
|
else if (lower.Contains("patch") ||
|
||||||
|
lower.Contains("патч"))
|
||||||
|
{
|
||||||
|
progress.Report(
|
||||||
|
(80, "Применение SpotX..."));
|
||||||
|
}
|
||||||
|
else if (lower.Contains("complete") ||
|
||||||
|
lower.Contains("готов"))
|
||||||
|
{
|
||||||
|
progress.Report(
|
||||||
|
(90, "Завершение..."));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ct);
|
||||||
|
|
||||||
|
ct.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// 5. RESULT
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
|
if (exitCode == 0)
|
||||||
|
{
|
||||||
|
progress.Report(
|
||||||
|
(
|
||||||
|
100,
|
||||||
|
isUninstall
|
||||||
|
? "Восстановление завершено"
|
||||||
|
: "Установка завершена"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
progress.Report(
|
||||||
|
(
|
||||||
|
100,
|
||||||
|
$"Ошибка установки (код {exitCode})"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return exitCode;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
try { if (File.Exists(scriptPath)) File.Delete(scriptPath); } catch { }
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(scriptPath))
|
||||||
|
File.Delete(scriptPath);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Игнорируем ошибку очистки временного файла.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Task<int> ExecAsync(string file, string[] argv,
|
private static async Task<int> ExecAsync(
|
||||||
Action<string> onLine, CancellationToken ct)
|
string file,
|
||||||
|
string[] arguments,
|
||||||
|
Action<string> onLine,
|
||||||
|
CancellationToken ct)
|
||||||
{
|
{
|
||||||
var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
||||||
var psi = new ProcessStartInfo
|
var psi = new ProcessStartInfo
|
||||||
{
|
{
|
||||||
FileName = file,
|
FileName = file,
|
||||||
RedirectStandardOutput = true,
|
RedirectStandardOutput = true,
|
||||||
RedirectStandardError = true,
|
RedirectStandardError = true,
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
CreateNoWindow = true,
|
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(() =>
|
foreach (string argument in arguments)
|
||||||
{
|
psi.ArgumentList.Add(argument);
|
||||||
try { if (!proc.HasExited) proc.Kill(entireProcessTree: true); }
|
|
||||||
catch { }
|
|
||||||
});
|
|
||||||
|
|
||||||
proc.Start();
|
using var process = new Process
|
||||||
proc.BeginOutputReadLine();
|
{
|
||||||
proc.BeginErrorReadLine();
|
StartInfo = psi
|
||||||
return tcs.Task;
|
};
|
||||||
|
|
||||||
|
process.OutputDataReceived += (_, e) =>
|
||||||
|
{
|
||||||
|
if (e.Data is not null)
|
||||||
|
onLine(e.Data);
|
||||||
|
};
|
||||||
|
|
||||||
|
process.ErrorDataReceived += (_, e) =>
|
||||||
|
{
|
||||||
|
if (e.Data is not null)
|
||||||
|
onLine(e.Data);
|
||||||
|
};
|
||||||
|
|
||||||
|
ct.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
if (!process.Start())
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Не удалось запустить процесс: {file}");
|
||||||
|
}
|
||||||
|
|
||||||
|
process.BeginOutputReadLine();
|
||||||
|
process.BeginErrorReadLine();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await process.WaitForExitAsync(ct)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
return process.ExitCode;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!process.HasExited)
|
||||||
|
{
|
||||||
|
process.Kill(
|
||||||
|
entireProcessTree: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Процесс уже мог завершиться.
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await process.WaitForExitAsync()
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Ignore.
|
||||||
|
}
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-1
@@ -1,9 +1,18 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
|
||||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
|
|
||||||
|
<AssemblyName>SpotXInstaller</AssemblyName>
|
||||||
|
<RootNamespace>SpotXInstaller</RootNamespace>
|
||||||
|
|
||||||
|
<InvariantGlobalization>false</InvariantGlobalization>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -16,10 +25,39 @@
|
|||||||
<PackageReference Include="Avalonia.Desktop" Version="12.1.2" />
|
<PackageReference Include="Avalonia.Desktop" Version="12.1.2" />
|
||||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="12.1.2" />
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="12.1.2" />
|
||||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="12.1.2" />
|
<PackageReference Include="Avalonia.Fonts.Inter" Version="12.1.2" />
|
||||||
|
|
||||||
|
<PackageReference Include="SkiaSharp.NativeAssets.macOS" Version="3.119.4" />
|
||||||
|
<PackageReference Include="SkiaSharp.NativeAssets.Win32" Version="3.119.4" />
|
||||||
|
|
||||||
<PackageReference Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.3">
|
<PackageReference Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.3">
|
||||||
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
|
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
|
||||||
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Windows -->
|
||||||
|
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'win-x64'">
|
||||||
|
<SelfContained>true</SelfContained>
|
||||||
|
<PublishSingleFile>true</PublishSingleFile>
|
||||||
|
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<!-- Linux -->
|
||||||
|
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64'">
|
||||||
|
<SelfContained>true</SelfContained>
|
||||||
|
<PublishSingleFile>true</PublishSingleFile>
|
||||||
|
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<!-- macOS:
|
||||||
|
НЕ включаем PublishSingleFile.
|
||||||
|
Avalonia native libraries должны находиться
|
||||||
|
внутри .app bundle. -->
|
||||||
|
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'osx-x64'">
|
||||||
|
<SelfContained>true</SelfContained>
|
||||||
|
<PublishSingleFile>false</PublishSingleFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
+285
-39
@@ -11,40 +11,102 @@ namespace SpotXInstaller.ViewModels;
|
|||||||
public partial class MainViewModel : ViewModelBase
|
public partial class MainViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
private readonly InstallerService _installer = new();
|
private readonly InstallerService _installer = new();
|
||||||
|
|
||||||
private CancellationTokenSource? _cts;
|
private CancellationTokenSource? _cts;
|
||||||
|
|
||||||
// --- СОСТОЯНИЯ ЭКРАНОВ ---
|
// ---------------------------------------------------------
|
||||||
[ObservableProperty] private bool _isStepLoading = true;
|
// SCREEN STATES
|
||||||
[ObservableProperty] private bool _isStepHome;
|
// ---------------------------------------------------------
|
||||||
[ObservableProperty] private bool _isStepSettings;
|
|
||||||
[ObservableProperty] private bool _isStepProgress;
|
|
||||||
|
|
||||||
// --- POPUP ---
|
[ObservableProperty]
|
||||||
[ObservableProperty] private bool _showPopup;
|
private bool _isStepLoading = true;
|
||||||
[ObservableProperty] private string _popupTitle = "";
|
|
||||||
[ObservableProperty] private string _popupMessage = "";
|
|
||||||
[ObservableProperty] private string _popupColor = "#8B5CF6";
|
|
||||||
|
|
||||||
// --- ГАЛОЧКИ ---
|
[ObservableProperty]
|
||||||
[ObservableProperty] private bool _blockUpdates = true;
|
private bool _isStepHome;
|
||||||
[ObservableProperty] private bool _hidePodcasts = true;
|
|
||||||
[ObservableProperty] private bool _hidePremium = true;
|
|
||||||
[ObservableProperty] private bool _clearCache;
|
|
||||||
|
|
||||||
// --- ПРОГРЕСС ---
|
[ObservableProperty]
|
||||||
[ObservableProperty] private double _progressValue;
|
private bool _isStepSettings;
|
||||||
[ObservableProperty] private string _progressStatus = "Подготовка...";
|
|
||||||
[ObservableProperty] private string _miniLog = "";
|
|
||||||
|
|
||||||
public MainViewModel() => _ = BootAsync();
|
[ObservableProperty]
|
||||||
|
private bool _isStepProgress;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// POPUP
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _showPopup;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _popupTitle = "";
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _popupMessage = "";
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _popupColor = "#8B5CF6";
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// SETTINGS
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _blockUpdates = true;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _hidePodcasts = true;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _hidePremium = true;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _clearCache;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// PROGRESS
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private double _progressValue;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _progressStatus = "Подготовка...";
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _miniLog = "";
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// STATE
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isInstalling;
|
||||||
|
|
||||||
|
public MainViewModel()
|
||||||
|
{
|
||||||
|
_ = BootAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// BOOT
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
private async Task BootAsync()
|
private async Task BootAsync()
|
||||||
{
|
{
|
||||||
await Task.Delay(3000);
|
await Task.Delay(3000);
|
||||||
|
|
||||||
SwitchTo(home: true);
|
SwitchTo(home: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SwitchTo(bool loading = false, bool home = false, bool settings = false, bool progress = false)
|
// ---------------------------------------------------------
|
||||||
|
// NAVIGATION
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
|
private void SwitchTo(
|
||||||
|
bool loading = false,
|
||||||
|
bool home = false,
|
||||||
|
bool settings = false,
|
||||||
|
bool progress = false)
|
||||||
{
|
{
|
||||||
IsStepLoading = loading;
|
IsStepLoading = loading;
|
||||||
IsStepHome = home;
|
IsStepHome = home;
|
||||||
@@ -52,9 +114,33 @@ public partial class MainViewModel : ViewModelBase
|
|||||||
IsStepProgress = progress;
|
IsStepProgress = progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand] private void GoToSettings() => SwitchTo(settings: true);
|
[RelayCommand]
|
||||||
[RelayCommand] private void GoBackToHome() => SwitchTo(home: true);
|
private void GoToSettings()
|
||||||
[RelayCommand] private void ClosePopup() => ShowPopup = false;
|
{
|
||||||
|
if (IsInstalling)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SwitchTo(settings: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void GoBackToHome()
|
||||||
|
{
|
||||||
|
if (IsInstalling)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SwitchTo(home: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void ClosePopup()
|
||||||
|
{
|
||||||
|
ShowPopup = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// INSTALL
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async Task StartInstallAsync()
|
private async Task StartInstallAsync()
|
||||||
@@ -62,68 +148,228 @@ public partial class MainViewModel : ViewModelBase
|
|||||||
await RunInstallerAsync(isUninstall: false);
|
await RunInstallerAsync(isUninstall: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// UNINSTALL
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async Task StartUninstallAsync()
|
private async Task StartUninstallAsync()
|
||||||
{
|
{
|
||||||
await RunInstallerAsync(isUninstall: true);
|
await RunInstallerAsync(isUninstall: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// CANCEL BUTTON
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task CancelInstallAsync()
|
||||||
|
{
|
||||||
|
if (!IsInstalling)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ProgressStatus = "Отмена установки...";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_cts?.Cancel();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Ignore.
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// WINDOW CLOSE
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
|
public async Task CancelInstallationAsync()
|
||||||
|
{
|
||||||
|
if (!IsInstalling)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ProgressStatus = "Завершение процессов...";
|
||||||
|
|
||||||
|
_cts?.Cancel();
|
||||||
|
|
||||||
|
// Даём process tree время корректно завершиться.
|
||||||
|
await Task.Delay(150);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Ignore.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// MAIN INSTALLER
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
private async Task RunInstallerAsync(bool isUninstall)
|
private async Task RunInstallerAsync(bool isUninstall)
|
||||||
{
|
{
|
||||||
|
if (IsInstalling)
|
||||||
|
return;
|
||||||
|
|
||||||
ProgressValue = 0;
|
ProgressValue = 0;
|
||||||
MiniLog = "";
|
MiniLog = "";
|
||||||
ProgressStatus = isUninstall ? "Подготовка восстановления..." : "Подготовка установки...";
|
|
||||||
|
ProgressStatus = isUninstall
|
||||||
|
? "Подготовка восстановления..."
|
||||||
|
: "Подготовка установки...";
|
||||||
|
|
||||||
SwitchTo(progress: true);
|
SwitchTo(progress: true);
|
||||||
|
|
||||||
_cts?.Dispose();
|
_cts?.Dispose();
|
||||||
_cts = new CancellationTokenSource();
|
_cts = new CancellationTokenSource();
|
||||||
|
|
||||||
var progress = new Progress<(int Percent, string Status)>(p =>
|
IsInstalling = true;
|
||||||
|
|
||||||
|
var progress =
|
||||||
|
new Progress<(int Percent, string Status)>(
|
||||||
|
p =>
|
||||||
{
|
{
|
||||||
ProgressValue = p.Percent;
|
ProgressValue = p.Percent;
|
||||||
ProgressStatus = p.Status;
|
ProgressStatus = p.Status;
|
||||||
});
|
});
|
||||||
var log = new Progress<string>(l => MiniLog = l);
|
|
||||||
|
var log =
|
||||||
|
new Progress<string>(
|
||||||
|
line =>
|
||||||
|
{
|
||||||
|
MiniLog = line;
|
||||||
|
});
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
int code = isUninstall
|
int code;
|
||||||
? await _installer.UninstallAsync(progress, log, _cts.Token)
|
|
||||||
: await _installer.InstallAsync(
|
if (isUninstall)
|
||||||
new InstallOptions(BlockUpdates, HidePodcasts, HidePremium, ClearCache),
|
{
|
||||||
progress, log, _cts.Token);
|
code = await _installer.UninstallAsync(
|
||||||
|
progress,
|
||||||
|
log,
|
||||||
|
_cts.Token);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
code = await _installer.InstallAsync(
|
||||||
|
new InstallOptions(
|
||||||
|
BlockUpdates,
|
||||||
|
HidePodcasts,
|
||||||
|
HidePremium,
|
||||||
|
ClearCache),
|
||||||
|
progress,
|
||||||
|
log,
|
||||||
|
_cts.Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
IsInstalling = false;
|
||||||
|
|
||||||
SwitchTo(home: true);
|
SwitchTo(home: true);
|
||||||
|
|
||||||
if (code == 0)
|
if (code == 0)
|
||||||
|
{
|
||||||
ShowNotification(
|
ShowNotification(
|
||||||
isUninstall ? "ГОТОВО" : "УСПЕХ",
|
isUninstall
|
||||||
|
? "ГОТОВО"
|
||||||
|
: "УСПЕХ",
|
||||||
|
|
||||||
isUninstall
|
isUninstall
|
||||||
? "Оригинальный клиент Spotify восстановлен."
|
? "Оригинальный клиент Spotify восстановлен."
|
||||||
: "SpotX успешно установлен.",
|
: "SpotX успешно установлен.",
|
||||||
isUninstall ? "#8B5CF6" : "#00E676");
|
|
||||||
|
isUninstall
|
||||||
|
? "#8B5CF6"
|
||||||
|
: "#00E676");
|
||||||
|
}
|
||||||
else
|
else
|
||||||
ShowNotification("ОШИБКА",
|
{
|
||||||
$"Скрипт завершился с кодом {code}.\n\n{MiniLog[..Math.Min(200, MiniLog.Length)]}",
|
string errorText =
|
||||||
|
MiniLog.Length > 500
|
||||||
|
? MiniLog[..500]
|
||||||
|
: MiniLog;
|
||||||
|
|
||||||
|
ShowNotification(
|
||||||
|
"ОШИБКА",
|
||||||
|
$"Скрипт завершился с кодом {code}.\n\n{errorText}",
|
||||||
"#FF3B30");
|
"#FF3B30");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
|
IsInstalling = false;
|
||||||
|
|
||||||
SwitchTo(home: true);
|
SwitchTo(home: true);
|
||||||
ShowNotification("ОТМЕНЕНО", "Операция прервана.", "#FFB300");
|
|
||||||
|
ShowNotification(
|
||||||
|
"ОТМЕНЕНО",
|
||||||
|
"Установка была отменена.",
|
||||||
|
"#FFB300");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
IsInstalling = false;
|
||||||
|
|
||||||
SwitchTo(home: true);
|
SwitchTo(home: true);
|
||||||
ShowNotification("ОШИБКА", ex.Message, "#FF3B30");
|
|
||||||
|
ShowNotification(
|
||||||
|
"ОШИБКА",
|
||||||
|
ex.Message,
|
||||||
|
"#FF3B30");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsInstalling = false;
|
||||||
|
|
||||||
|
_cts?.Dispose();
|
||||||
|
_cts = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShowNotification(string title, string message, string hexColor)
|
// ---------------------------------------------------------
|
||||||
|
// POPUP
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
|
private void ShowNotification(
|
||||||
|
string title,
|
||||||
|
string message,
|
||||||
|
string hexColor)
|
||||||
{
|
{
|
||||||
PopupTitle = title;
|
PopupTitle = title;
|
||||||
PopupMessage = message;
|
PopupMessage = message;
|
||||||
PopupColor = hexColor;
|
PopupColor = hexColor;
|
||||||
ShowPopup = true;
|
ShowPopup = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// DISPOSE
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
|
public async Task DisposeAsync()
|
||||||
|
{
|
||||||
|
if (!IsInstalling)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_cts?.Cancel();
|
||||||
|
|
||||||
|
await Task.Delay(150);
|
||||||
}
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Ignore.
|
||||||
|
}
|
||||||
|
|
||||||
|
_cts?.Dispose();
|
||||||
|
_cts = null;
|
||||||
|
|
||||||
|
IsInstalling = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,43 @@
|
|||||||
|
using System;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using SpotXInstaller.ViewModels;
|
||||||
|
|
||||||
namespace SpotXInstaller.Views;
|
namespace SpotXInstaller.Views;
|
||||||
|
|
||||||
public partial class MainWindow : Window
|
public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
|
private bool _allowClose;
|
||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
|
Closing += MainWindow_Closing;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void MainWindow_Closing(
|
||||||
|
object? sender,
|
||||||
|
WindowClosingEventArgs e)
|
||||||
|
{
|
||||||
|
if (_allowClose)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (DataContext is not MainViewModel vm)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!vm.IsInstalling)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Не закрываем окно мгновенно.
|
||||||
|
// Сначала отменяем установку и убиваем process tree.
|
||||||
|
e.Cancel = true;
|
||||||
|
|
||||||
|
await vm.CancelInstallationAsync();
|
||||||
|
|
||||||
|
_allowClose = true;
|
||||||
|
|
||||||
|
Close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Executable
+121
@@ -0,0 +1,121 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
APP_NAME="SpotXInstaller"
|
||||||
|
RID="linux-x64"
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
|
||||||
|
PUBLISH="$ROOT/bin/Release/net10.0/$RID/publish"
|
||||||
|
APPDIR="$ROOT/bin/Release/net10.0/$RID/AppDir"
|
||||||
|
OUTPUT="$ROOT/bin/Release/net10.0/$APP_NAME-x86_64.AppImage"
|
||||||
|
|
||||||
|
ICON="$ROOT/Assets/icons/SpotXInstaller.png"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "========================================"
|
||||||
|
echo " Building SpotXInstaller AppImage"
|
||||||
|
echo "========================================"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "[1/5] Cleaning..."
|
||||||
|
|
||||||
|
rm -rf "$PUBLISH"
|
||||||
|
rm -rf "$APPDIR"
|
||||||
|
rm -f "$OUTPUT"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "[2/5] Publishing Linux application..."
|
||||||
|
|
||||||
|
dotnet publish "$ROOT/SpotXInstaller.csproj" \
|
||||||
|
-c Release \
|
||||||
|
-r "$RID" \
|
||||||
|
--self-contained true \
|
||||||
|
-p:PublishSingleFile=false \
|
||||||
|
-p:IncludeNativeLibrariesForSelfExtract=false \
|
||||||
|
-o "$PUBLISH"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "[3/5] Creating AppDir..."
|
||||||
|
|
||||||
|
mkdir -p "$APPDIR/usr/bin"
|
||||||
|
mkdir -p "$APPDIR/usr/lib"
|
||||||
|
mkdir -p "$APPDIR/usr/share/applications"
|
||||||
|
|
||||||
|
cp -R "$PUBLISH/." "$APPDIR/usr/bin/"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "[4/5] Creating AppImage files..."
|
||||||
|
|
||||||
|
# Проверяем наличие иконки
|
||||||
|
if [ ! -f "$ICON" ]; then
|
||||||
|
echo
|
||||||
|
echo "ERROR: Icon not found:"
|
||||||
|
echo "$ICON"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Иконка должна находиться в корне AppDir
|
||||||
|
cp "$ICON" "$APPDIR/SpotXInstaller.png"
|
||||||
|
|
||||||
|
# Проверяем, что иконка действительно скопирована
|
||||||
|
if [ ! -f "$APPDIR/SpotXInstaller.png" ]; then
|
||||||
|
echo
|
||||||
|
echo "ERROR: Failed to copy icon."
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat > "$APPDIR/AppRun" <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
HERE="$(dirname "$(readlink -f "$0")")"
|
||||||
|
|
||||||
|
export LD_LIBRARY_PATH="$HERE/usr/bin:$HERE/usr/lib:${LD_LIBRARY_PATH:-}"
|
||||||
|
|
||||||
|
exec "$HERE/usr/bin/SpotXInstaller" "$@"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x "$APPDIR/AppRun"
|
||||||
|
|
||||||
|
cat > "$APPDIR/SpotXInstaller.desktop" <<'EOF'
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=SpotX Installer
|
||||||
|
Comment=SpotX Spotify Installer
|
||||||
|
Exec=SpotXInstaller
|
||||||
|
Icon=SpotXInstaller
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
Categories=Utility;
|
||||||
|
StartupNotify=true
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cp "$APPDIR/SpotXInstaller.desktop" \
|
||||||
|
"$APPDIR/usr/share/applications/SpotXInstaller.desktop"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "AppDir contents:"
|
||||||
|
echo " AppRun: $APPDIR/AppRun"
|
||||||
|
echo " Icon: $APPDIR/SpotXInstaller.png"
|
||||||
|
echo " Desktop: $APPDIR/SpotXInstaller.desktop"
|
||||||
|
|
||||||
|
echo
|
||||||
|
ls -lh "$APPDIR/SpotXInstaller.png"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "[5/5] Building AppImage..."
|
||||||
|
|
||||||
|
appimagetool "$APPDIR" "$OUTPUT"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "========================================"
|
||||||
|
echo " BUILD COMPLETE"
|
||||||
|
echo "========================================"
|
||||||
|
echo
|
||||||
|
echo "AppImage:"
|
||||||
|
echo "$OUTPUT"
|
||||||
|
echo
|
||||||
|
ls -lh "$OUTPUT"
|
||||||
|
echo
|
||||||
Executable
+68
@@ -0,0 +1,68 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
APP_NAME="SpotXInstaller"
|
||||||
|
RID="osx-x64"
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
|
||||||
|
OUTPUT_DIR="$ROOT/bin/Release/net10.0/$RID/publish"
|
||||||
|
ZIP_DIR="$ROOT/bin/Release/net10.0/$RID"
|
||||||
|
ZIP_FILE="$ZIP_DIR/$APP_NAME-macOS-x64.zip"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "========================================"
|
||||||
|
echo " Building SpotXInstaller macOS"
|
||||||
|
echo "========================================"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "[1/4] Cleaning..."
|
||||||
|
|
||||||
|
rm -rf "$OUTPUT_DIR"
|
||||||
|
rm -f "$ZIP_FILE"
|
||||||
|
|
||||||
|
mkdir -p "$ZIP_DIR"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "[2/4] Publishing macOS Intel..."
|
||||||
|
|
||||||
|
dotnet publish "$ROOT/SpotXInstaller.csproj" \
|
||||||
|
-c Release \
|
||||||
|
-r "$RID" \
|
||||||
|
--self-contained true \
|
||||||
|
-p:PublishSingleFile=false \
|
||||||
|
-p:IncludeNativeLibrariesForSelfExtract=false \
|
||||||
|
-p:DebugType=None \
|
||||||
|
-p:DebugSymbols=false \
|
||||||
|
-o "$OUTPUT_DIR"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "[3/4] Creating ZIP..."
|
||||||
|
|
||||||
|
cd "$OUTPUT_DIR"
|
||||||
|
|
||||||
|
zip -r "$ZIP_FILE" . \
|
||||||
|
-x "*.pdb"
|
||||||
|
|
||||||
|
cd "$ROOT"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "[4/4] Checking output..."
|
||||||
|
|
||||||
|
if [ ! -f "$ZIP_FILE" ]; then
|
||||||
|
echo
|
||||||
|
echo "ERROR: macOS ZIP was not created."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "========================================"
|
||||||
|
echo " MACOS BUILD COMPLETE"
|
||||||
|
echo "========================================"
|
||||||
|
echo
|
||||||
|
echo "ZIP:"
|
||||||
|
echo "$ZIP_FILE"
|
||||||
|
echo
|
||||||
|
ls -lh "$ZIP_FILE"
|
||||||
|
echo
|
||||||
Executable
+54
@@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
APP_NAME="SpotXInstaller"
|
||||||
|
RID="win-x64"
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
|
||||||
|
OUTPUT_DIR="$ROOT/bin/Release/net10.0/$RID/publish"
|
||||||
|
OUTPUT_EXE="$OUTPUT_DIR/$APP_NAME.exe"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "========================================"
|
||||||
|
echo " Building SpotXInstaller Windows"
|
||||||
|
echo "========================================"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "[1/3] Cleaning..."
|
||||||
|
|
||||||
|
rm -rf "$OUTPUT_DIR"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "[2/3] Publishing Windows x64..."
|
||||||
|
|
||||||
|
dotnet publish "$ROOT/SpotXInstaller.csproj" \
|
||||||
|
-c Release \
|
||||||
|
-r "$RID" \
|
||||||
|
--self-contained true \
|
||||||
|
-p:PublishSingleFile=true \
|
||||||
|
-p:IncludeNativeLibrariesForSelfExtract=true \
|
||||||
|
-p:DebugType=None \
|
||||||
|
-p:DebugSymbols=false \
|
||||||
|
-o "$OUTPUT_DIR"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "[3/3] Checking output..."
|
||||||
|
|
||||||
|
if [ ! -f "$OUTPUT_EXE" ]; then
|
||||||
|
echo
|
||||||
|
echo "ERROR: Windows executable was not created."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "========================================"
|
||||||
|
echo " WINDOWS BUILD COMPLETE"
|
||||||
|
echo "========================================"
|
||||||
|
echo
|
||||||
|
echo "EXE:"
|
||||||
|
echo "$OUTPUT_EXE"
|
||||||
|
echo
|
||||||
|
ls -lh "$OUTPUT_EXE"
|
||||||
|
echo
|
||||||
Reference in New Issue
Block a user