From fc9125066318a7bae75c08f9b81be866f33501a2 Mon Sep 17 00:00:00 2001 From: kirieska Date: Mon, 14 Sep 2026 22:34:33 +0200 Subject: [PATCH] =?UTF-8?q?=D1=84=D0=B8=D0=BD=D0=B0=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/icons/SpotXInstaller.png | Bin 0 -> 713 bytes Services/InstallerService.cs | 95 ++++++++++-------------- SpotXInstaller.csproj | 125 ++++++++++++++++---------------- scripts/build-appimage.sh | 121 +++++++++++++++++++++++++++++++ scripts/build-macos.sh | 68 +++++++++++++++++ scripts/build-windows.sh | 54 ++++++++++++++ 6 files changed, 343 insertions(+), 120 deletions(-) create mode 100644 Assets/icons/SpotXInstaller.png create mode 100755 scripts/build-appimage.sh create mode 100755 scripts/build-macos.sh create mode 100755 scripts/build-windows.sh diff --git a/Assets/icons/SpotXInstaller.png b/Assets/icons/SpotXInstaller.png new file mode 100644 index 0000000000000000000000000000000000000000..c5b4c430052b10d30a82c59bd203051bd0d002e1 GIT binary patch literal 713 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4zl0AZa85pY67#JE_7#My5g&JNk zFq9fFFuY1&V6d9Oz#v{QXIG#NP=YDR+ueoXe|!I#{XiajiKnkC`zua9VQo2<+fk}O zA=MJsh?11Vl2ohYqEsNoU}RuqrfXoSYiJT;XlZ3)Y-MbqZD3$!U|{3KG#y1lZhlH; zS|x4`zVf@Yff_X6Hk4%MrWThZ<`&@ABgHlQ3j+h=T2B|p5Rc^S_(CoJq#ql{qSovcuu?>CvejB;RX%9q5}sL&4WH| zwB|c~t2XW0zEAT{pLt*T{Q8;CcE${5TP7TKH#QQxcIX;QpYA2awikxU#uMa{jxSc~ zIM_9}!qNZ5Q>}o$X=2Mfgx)m8-MRnoe(+(Qmrb#kk6%2`{BK&3%oC2X(+s~C=1W+z zT4;*AX-tfHsM|F~M{I?_neMr~HMcdrnOd930H%w~s=BVj$f8E%oUh%J`*?r@_`9)h}IZ~$kI&P9wj*R`# zHre{1R7R-y0Zr{D6$7VzyBv~d8XxSEIJQQsL0YWz|02FuS6MflYgEwenYdE#gTeXD p2~)o$1pSxVepB#>i@E9t&W5zLlh5CKX90|C22WQ%mvv4FO#ow|80`Q6 literal 0 HcmV?d00001 diff --git a/Services/InstallerService.cs b/Services/InstallerService.cs index 2cd2a6e..ee4ace5 100644 --- a/Services/InstallerService.cs +++ b/Services/InstallerService.cs @@ -145,11 +145,21 @@ public sealed class InstallerService if (!isUninstall) { + // 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 + arguments.Add("-block_update_off"); if (options.HidePodcasts) arguments.Add("-podcasts_off"); + else + arguments.Add("-podcasts_on"); if (options.HidePremium) arguments.Add("-adsections_off"); @@ -205,8 +215,6 @@ public sealed class InstallerService string lower = line.ToLowerInvariant(); - // Никакого фальшивого +2%. - // Только изменение текста статуса по реальному выводу. if (lower.Contains("download") || lower.Contains("скач")) { @@ -243,16 +251,20 @@ public sealed class InstallerService if (exitCode == 0) { progress.Report( - (100, + ( + 100, isUninstall ? "Восстановление завершено" - : "Установка завершена")); + : "Установка завершена" + )); } else { progress.Report( - (100, - $"Ошибка установки (код {exitCode})")); + ( + 100, + $"Ошибка установки (код {exitCode})" + )); } return exitCode; @@ -277,17 +289,11 @@ public sealed class InstallerService Action onLine, CancellationToken ct) { - var tcs = - new TaskCompletionSource( - TaskCreationOptions.RunContinuationsAsynchronously); - var psi = new ProcessStartInfo { FileName = file, - RedirectStandardOutput = true, RedirectStandardError = true, - UseShellExecute = false, CreateNoWindow = true }; @@ -295,14 +301,11 @@ public sealed class InstallerService foreach (string argument in arguments) psi.ArgumentList.Add(argument); - var process = new Process + using var process = new Process { - StartInfo = psi, - EnableRaisingEvents = true + StartInfo = psi }; - int finished = 0; - process.OutputDataReceived += (_, e) => { if (e.Data is not null) @@ -315,31 +318,26 @@ public sealed class InstallerService onLine(e.Data); }; - process.Exited += (_, _) => + ct.ThrowIfCancellationRequested(); + + if (!process.Start()) { - if (Interlocked.Exchange(ref finished, 1) != 0) - return; + throw new InvalidOperationException( + $"Не удалось запустить процесс: {file}"); + } - try - { - tcs.TrySetResult(process.ExitCode); - } - catch - { - tcs.TrySetResult(-1); - } - finally - { - process.Dispose(); - } - }; + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); - using CancellationTokenRegistration registration = - ct.Register(() => + try { - if (Interlocked.Exchange(ref finished, 1) != 0) - return; + await process.WaitForExitAsync(ct) + .ConfigureAwait(false); + return process.ExitCode; + } + catch (OperationCanceledException) + { try { if (!process.HasExited) @@ -353,28 +351,10 @@ public sealed class InstallerService // Процесс уже мог завершиться. } - tcs.TrySetCanceled(ct); - }); - - try - { - ct.ThrowIfCancellationRequested(); - - if (!process.Start()) - throw new InvalidOperationException( - $"Не удалось запустить процесс: {file}"); - - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - - return await tcs.Task.ConfigureAwait(false); - } - catch - { try { - if (!process.HasExited) - process.Kill(entireProcessTree: true); + await process.WaitForExitAsync() + .ConfigureAwait(false); } catch { @@ -385,4 +365,3 @@ public sealed class InstallerService } } } - diff --git a/SpotXInstaller.csproj b/SpotXInstaller.csproj index 17b27cf..a160d39 100644 --- a/SpotXInstaller.csproj +++ b/SpotXInstaller.csproj @@ -1,62 +1,63 @@ - - - - Exe - net10.0 - - enable - enable - - app.manifest - - SpotXInstaller - SpotXInstaller - - false - - - - - - - - - - - - - - - - - - - None - All - - - - - - - - true - true - true - - - - - true - true - true - - - - - true - true - - - - + + + + Exe + net10.0 + + enable + enable + + app.manifest + + SpotXInstaller + SpotXInstaller + + false + + + + + + + + + + + + + + + + + + None + All + + + + + + + + true + true + true + + + + + true + true + true + + + + + true + false + + + diff --git a/scripts/build-appimage.sh b/scripts/build-appimage.sh new file mode 100755 index 0000000..66e9f13 --- /dev/null +++ b/scripts/build-appimage.sh @@ -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 diff --git a/scripts/build-macos.sh b/scripts/build-macos.sh new file mode 100755 index 0000000..9ec2b90 --- /dev/null +++ b/scripts/build-macos.sh @@ -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 diff --git a/scripts/build-windows.sh b/scripts/build-windows.sh new file mode 100755 index 0000000..b052c00 --- /dev/null +++ b/scripts/build-windows.sh @@ -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