Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3413e78166 | ||
|
|
fc91250663 | ||
|
|
45e8e4873a | ||
|
|
30f9e3d1e7 |
Binary file not shown.
|
After Width: | Height: | Size: 713 B |
@@ -145,11 +145,21 @@ public sealed class InstallerService
|
|||||||
|
|
||||||
if (!isUninstall)
|
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)
|
if (options.BlockUpdates)
|
||||||
arguments.Add("-block_update_on");
|
arguments.Add("-block_update_on");
|
||||||
|
else
|
||||||
|
arguments.Add("-block_update_off");
|
||||||
|
|
||||||
if (options.HidePodcasts)
|
if (options.HidePodcasts)
|
||||||
arguments.Add("-podcasts_off");
|
arguments.Add("-podcasts_off");
|
||||||
|
else
|
||||||
|
arguments.Add("-podcasts_on");
|
||||||
|
|
||||||
if (options.HidePremium)
|
if (options.HidePremium)
|
||||||
arguments.Add("-adsections_off");
|
arguments.Add("-adsections_off");
|
||||||
@@ -205,8 +215,6 @@ public sealed class InstallerService
|
|||||||
|
|
||||||
string lower = line.ToLowerInvariant();
|
string lower = line.ToLowerInvariant();
|
||||||
|
|
||||||
// Никакого фальшивого +2%.
|
|
||||||
// Только изменение текста статуса по реальному выводу.
|
|
||||||
if (lower.Contains("download") ||
|
if (lower.Contains("download") ||
|
||||||
lower.Contains("скач"))
|
lower.Contains("скач"))
|
||||||
{
|
{
|
||||||
@@ -243,16 +251,20 @@ public sealed class InstallerService
|
|||||||
if (exitCode == 0)
|
if (exitCode == 0)
|
||||||
{
|
{
|
||||||
progress.Report(
|
progress.Report(
|
||||||
(100,
|
(
|
||||||
|
100,
|
||||||
isUninstall
|
isUninstall
|
||||||
? "Восстановление завершено"
|
? "Восстановление завершено"
|
||||||
: "Установка завершена"));
|
: "Установка завершена"
|
||||||
|
));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
progress.Report(
|
progress.Report(
|
||||||
(100,
|
(
|
||||||
$"Ошибка установки (код {exitCode})"));
|
100,
|
||||||
|
$"Ошибка установки (код {exitCode})"
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
return exitCode;
|
return exitCode;
|
||||||
@@ -277,17 +289,11 @@ public sealed class InstallerService
|
|||||||
Action<string> onLine,
|
Action<string> onLine,
|
||||||
CancellationToken ct)
|
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
|
||||||
};
|
};
|
||||||
@@ -295,14 +301,11 @@ public sealed class InstallerService
|
|||||||
foreach (string argument in arguments)
|
foreach (string argument in arguments)
|
||||||
psi.ArgumentList.Add(argument);
|
psi.ArgumentList.Add(argument);
|
||||||
|
|
||||||
var process = new Process
|
using var process = new Process
|
||||||
{
|
{
|
||||||
StartInfo = psi,
|
StartInfo = psi
|
||||||
EnableRaisingEvents = true
|
|
||||||
};
|
};
|
||||||
|
|
||||||
int finished = 0;
|
|
||||||
|
|
||||||
process.OutputDataReceived += (_, e) =>
|
process.OutputDataReceived += (_, e) =>
|
||||||
{
|
{
|
||||||
if (e.Data is not null)
|
if (e.Data is not null)
|
||||||
@@ -315,31 +318,26 @@ public sealed class InstallerService
|
|||||||
onLine(e.Data);
|
onLine(e.Data);
|
||||||
};
|
};
|
||||||
|
|
||||||
process.Exited += (_, _) =>
|
ct.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
if (!process.Start())
|
||||||
{
|
{
|
||||||
if (Interlocked.Exchange(ref finished, 1) != 0)
|
throw new InvalidOperationException(
|
||||||
return;
|
$"Не удалось запустить процесс: {file}");
|
||||||
|
}
|
||||||
|
|
||||||
try
|
process.BeginOutputReadLine();
|
||||||
{
|
process.BeginErrorReadLine();
|
||||||
tcs.TrySetResult(process.ExitCode);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
tcs.TrySetResult(-1);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
process.Dispose();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
using CancellationTokenRegistration registration =
|
try
|
||||||
ct.Register(() =>
|
|
||||||
{
|
{
|
||||||
if (Interlocked.Exchange(ref finished, 1) != 0)
|
await process.WaitForExitAsync(ct)
|
||||||
return;
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
return process.ExitCode;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!process.HasExited)
|
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
|
try
|
||||||
{
|
{
|
||||||
if (!process.HasExited)
|
await process.WaitForExitAsync()
|
||||||
process.Kill(entireProcessTree: true);
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -385,4 +365,3 @@ public sealed class InstallerService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
<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" />
|
||||||
|
|
||||||
<!-- Native Skia assets -->
|
|
||||||
<PackageReference Include="SkiaSharp.NativeAssets.macOS" Version="3.119.4" />
|
<PackageReference Include="SkiaSharp.NativeAssets.macOS" Version="3.119.4" />
|
||||||
<PackageReference Include="SkiaSharp.NativeAssets.Win32" Version="3.119.4" />
|
<PackageReference Include="SkiaSharp.NativeAssets.Win32" Version="3.119.4" />
|
||||||
|
|
||||||
@@ -38,26 +37,27 @@
|
|||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- Windows x64 -->
|
<!-- Windows -->
|
||||||
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'win-x64'">
|
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'win-x64'">
|
||||||
<SelfContained>true</SelfContained>
|
<SelfContained>true</SelfContained>
|
||||||
<PublishSingleFile>true</PublishSingleFile>
|
<PublishSingleFile>true</PublishSingleFile>
|
||||||
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<!-- Linux x64 -->
|
<!-- Linux -->
|
||||||
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64'">
|
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64'">
|
||||||
<SelfContained>true</SelfContained>
|
<SelfContained>true</SelfContained>
|
||||||
<PublishSingleFile>true</PublishSingleFile>
|
<PublishSingleFile>true</PublishSingleFile>
|
||||||
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<!-- macOS Intel -->
|
<!-- macOS:
|
||||||
|
НЕ включаем PublishSingleFile.
|
||||||
|
Avalonia native libraries должны находиться
|
||||||
|
внутри .app bundle. -->
|
||||||
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'osx-x64'">
|
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'osx-x64'">
|
||||||
<SelfContained>true</SelfContained>
|
<SelfContained>true</SelfContained>
|
||||||
<PublishSingleFile>true</PublishSingleFile>
|
<PublishSingleFile>false</PublishSingleFile>
|
||||||
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
||||||
|
|||||||
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