финал

This commit is contained in:
2026-09-14 22:34:33 +02:00
parent 45e8e4873a
commit fc91250663
6 changed files with 343 additions and 120 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 713 B

+37 -58
View File
@@ -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<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
};
@@ -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
}
}
}
+63 -62
View File
@@ -1,62 +1,63 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AssemblyName>SpotXInstaller</AssemblyName>
<RootNamespace>SpotXInstaller</RootNamespace>
<InvariantGlobalization>false</InvariantGlobalization>
</PropertyGroup>
<ItemGroup>
<Folder Include="Models\" />
<AvaloniaResource Include="Assets\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="12.1.2" />
<PackageReference Include="Avalonia.Desktop" Version="12.1.2" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="12.1.2" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="12.1.2" />
<!-- Native Skia assets -->
<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">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
</ItemGroup>
<!-- Windows x64 -->
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'win-x64'">
<SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
</PropertyGroup>
<!-- Linux x64 -->
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64'">
<SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
</PropertyGroup>
<!-- macOS Intel -->
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'osx-x64'">
<SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
</PropertyGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AssemblyName>SpotXInstaller</AssemblyName>
<RootNamespace>SpotXInstaller</RootNamespace>
<InvariantGlobalization>false</InvariantGlobalization>
</PropertyGroup>
<ItemGroup>
<Folder Include="Models\" />
<AvaloniaResource Include="Assets\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="12.1.2" />
<PackageReference Include="Avalonia.Desktop" Version="12.1.2" />
<PackageReference Include="Avalonia.Themes.Fluent" 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">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
</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>
+121
View File
@@ -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
+68
View File
@@ -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
+54
View File
@@ -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