Compare commits
187 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa537c63d9 | ||
|
|
3f6cba6bdd | ||
|
|
65dcb761bd | ||
|
|
df4be06104 | ||
|
|
a6c9716521 | ||
|
|
86ea359ab0 | ||
|
|
2f207d2667 | ||
|
|
d26ef77ead | ||
|
|
a174eda2e0 | ||
|
|
cc256be6ac | ||
|
|
2892713442 | ||
|
|
acab906723 | ||
|
|
7728100b3b | ||
|
|
6ccbafbb47 | ||
|
|
162fa66840 | ||
|
|
7d8b10ba7d | ||
|
|
fcc8e2b822 | ||
|
|
fe889d1026 | ||
|
|
8728576c0c | ||
|
|
3a6a75235c | ||
|
|
c197397e58 | ||
|
|
9c21bf44bf | ||
|
|
f8fc5f57a0 | ||
|
|
3b72261622 | ||
|
|
61c8175e2a | ||
|
|
5002fadff1 | ||
|
|
45bc01e8bd | ||
|
|
5618c88aaf | ||
|
|
72054c2fec | ||
|
|
c73b6f8c90 | ||
|
|
02c04bbbda | ||
|
|
a5b9f1ff60 | ||
|
|
d683626a49 | ||
|
|
9b82042f98 | ||
|
|
0618b8d452 | ||
|
|
ad30f8389b | ||
|
|
d10399826f | ||
|
|
90c98c0eb8 | ||
|
|
12e0093856 | ||
|
|
80e6947c87 | ||
|
|
88e997b6dd | ||
|
|
ca7de1e166 | ||
|
|
7e70f4f339 | ||
|
|
41d837b5b0 | ||
|
|
f399b5cdd6 | ||
|
|
7c071de816 | ||
|
|
be781c007e | ||
|
|
43e10f7e0a | ||
|
|
392a0235f8 | ||
|
|
b25f0cf848 | ||
|
|
7c537a68a0 | ||
|
|
3fd9169c15 | ||
|
|
3bb8caf576 | ||
|
|
fda738dde1 | ||
|
|
c255e9eab8 | ||
|
|
4e61673309 | ||
|
|
e179da8e48 | ||
|
|
9e079d0968 | ||
|
|
5fba045c3a | ||
|
|
41a318e417 | ||
|
|
80bb785d07 | ||
|
|
319354b1ac | ||
|
|
501a8c9877 | ||
|
|
88e67df823 | ||
|
|
2cdf95f4ad | ||
|
|
84854e9bf3 | ||
|
|
59e6d4f47b | ||
|
|
6a1219a76e | ||
|
|
5f3103d908 | ||
|
|
18e8130d04 | ||
|
|
0874d7f305 | ||
|
|
8d610ceaad | ||
|
|
29b9b1e37e | ||
|
|
18774fa88b | ||
|
|
f450ca818c | ||
|
|
97fff96bd7 | ||
|
|
225d47e09e | ||
|
|
1caa61daa7 | ||
|
|
8b3fce1ec2 | ||
|
|
e060b29562 | ||
|
|
47258b2b26 | ||
|
|
ed8fbaf286 | ||
|
|
43ce7f9302 | ||
|
|
27ecd89f27 | ||
|
|
f4200c56a6 | ||
|
|
ee2288360e | ||
|
|
27d16425b7 | ||
|
|
f7bc952f82 | ||
|
|
07cf7fea90 | ||
|
|
e5cd965530 | ||
|
|
43271f0004 | ||
|
|
8d94b700f1 | ||
|
|
ee6cc93a0b | ||
|
|
b89472d397 | ||
|
|
a505191e0e | ||
|
|
387b329651 | ||
|
|
ca9b8c328b | ||
|
|
9a887ca2d0 | ||
|
|
947986fd6c | ||
|
|
d6fb0f0483 | ||
|
|
1fb610a32e | ||
|
|
6e8aeba93d | ||
|
|
ff58033e51 | ||
|
|
1c69604346 | ||
|
|
660715dd7b | ||
|
|
06ba92fc54 | ||
|
|
5717fb178c | ||
|
|
c77736ccc5 | ||
|
|
549117406c | ||
|
|
820937d353 | ||
|
|
7af18cd8b2 | ||
|
|
815d92cd48 | ||
|
|
4a0a7bb0de | ||
|
|
d4981cf75c | ||
|
|
e61063df56 | ||
|
|
a9433d2f73 | ||
|
|
5f4ac6a10c | ||
|
|
8e49fe7dd7 | ||
|
|
9046d7d8c2 | ||
|
|
fe1af720e3 | ||
|
|
f3d0d53e20 | ||
|
|
0d00cac03f | ||
|
|
4847fa31bd | ||
|
|
259f31a1d3 | ||
|
|
a2fd6913cc | ||
|
|
e6b410b837 | ||
|
|
39b110ff83 | ||
|
|
fff751fbcc | ||
|
|
871ebfb35f | ||
|
|
b8c01ad340 | ||
|
|
dee58698c7 | ||
|
|
f4b24453ac | ||
|
|
d699d2ac8c | ||
|
|
3f952f65bc | ||
|
|
1e1fbb6fcb | ||
|
|
8dd85ffcfd | ||
|
|
2a7d1845eb | ||
|
|
172b066e95 | ||
|
|
2c5e18b40c | ||
|
|
f7ebdc8915 | ||
|
|
e7f647aa3c | ||
|
|
4b936d86a4 | ||
|
|
26344b96b4 | ||
|
|
5b24d6d776 | ||
|
|
6cbe6ccdaf | ||
|
|
6507e63825 | ||
|
|
d8e2291f0f | ||
|
|
3268102583 | ||
|
|
902b6f1223 | ||
|
|
e6d3f0c76e | ||
|
|
336fe44b5b | ||
|
|
187fc4d439 | ||
|
|
5eee2572e2 | ||
|
|
0eebb89e76 | ||
|
|
d15b5fbc32 | ||
|
|
0398cb6ea1 | ||
|
|
d61a2e58db | ||
|
|
e0cf1f94fc | ||
|
|
430d5d40fc | ||
|
|
ab8896961f | ||
|
|
ba0af93cdc | ||
|
|
86d02dde3c | ||
|
|
31eeb1d86d | ||
|
|
cb81917067 | ||
|
|
4aeb2f150f | ||
|
|
817cefb17e | ||
|
|
90bc45dc0f | ||
|
|
51ff38d16f | ||
|
|
ade13834f9 | ||
|
|
c1c0c715d1 | ||
|
|
4e0009ce26 | ||
|
|
bffdc39cca | ||
|
|
2d561118b9 | ||
|
|
70f3ac3249 | ||
|
|
8806f679a9 | ||
|
|
ea015c7280 | ||
|
|
0b7ae2e90a | ||
|
|
e35724cf7f | ||
|
|
6c882d93c6 | ||
|
|
4911d56f18 | ||
|
|
6a235cd0f5 | ||
|
|
9070044b8a | ||
|
|
923b4b8d78 | ||
|
|
4367d89cbe | ||
|
|
9053695b6b | ||
|
|
467b7301ec | ||
|
|
432ad0301e |
16 changed files with 2377 additions and 356 deletions
158
.github/workflows/build-to-release.yml
vendored
158
.github/workflows/build-to-release.yml
vendored
|
|
@ -17,14 +17,24 @@ jobs:
|
||||||
rust_target: aarch64-pc-windows-msvc
|
rust_target: aarch64-pc-windows-msvc
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Install Rust toolchain and target
|
- name: Install Rust toolchain
|
||||||
shell: pwsh
|
uses: dtolnay/rust-toolchain@stable
|
||||||
run: |
|
with:
|
||||||
rustup toolchain install stable --profile minimal
|
targets: ${{ matrix.rust_target }}
|
||||||
rustup target add ${{ matrix.rust_target }}
|
|
||||||
rustup default stable
|
- name: Cache cargo dependencies
|
||||||
|
uses: actions/cache@v5
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
LoaderSpot_UI/target
|
||||||
|
LoaderSpot_CLI/target
|
||||||
|
key: ${{ runner.os }}-cargo-${{ matrix.rust_target }}-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-cargo-${{ matrix.rust_target }}-
|
||||||
|
|
||||||
- name: Build release binaries
|
- name: Build release binaries
|
||||||
env:
|
env:
|
||||||
|
|
@ -32,93 +42,87 @@ jobs:
|
||||||
ARCH: ${{ matrix.arch }}
|
ARCH: ${{ matrix.arch }}
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
# fail on first error
|
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
# prepare artifacts folder
|
|
||||||
$out = "artifacts"
|
$out = "artifacts"
|
||||||
if (Test-Path $out) { Remove-Item $out -Recurse -Force }
|
if (Test-Path $out) { Remove-Item $out -Recurse -Force }
|
||||||
New-Item -ItemType Directory -Path $out | Out-Null
|
New-Item -ItemType Directory -Path $out | Out-Null
|
||||||
|
|
||||||
# build UI
|
Write-Host "Building UI for $env:RUST_TARGET"
|
||||||
Write-Host "Building UI for target: $env:RUST_TARGET"
|
|
||||||
cargo build --manifest-path LoaderSpot_UI/Cargo.toml --release --target $env:RUST_TARGET
|
cargo build --manifest-path LoaderSpot_UI/Cargo.toml --release --target $env:RUST_TARGET
|
||||||
$exePathUI = Join-Path -Path "LoaderSpot_UI" -ChildPath (Join-Path -Path "target" -ChildPath (Join-Path -Path $env:RUST_TARGET -ChildPath "release"))
|
$exeUI = Get-ChildItem "LoaderSpot_UI/target/$env:RUST_TARGET/release/*.exe" | Select-Object -First 1
|
||||||
$exeUI = Get-ChildItem -Path $exePathUI -Filter "*.exe" -File | Select-Object -First 1
|
Copy-Item $exeUI.FullName "$out\loaderspot-ui-win-$env:ARCH.exe"
|
||||||
Copy-Item $exeUI.FullName -Destination "$out\loaderspot_ui_$env:ARCH.exe" -Force
|
Write-Host "UI executable ready: loaderspot-ui-win-$env:ARCH.exe"
|
||||||
Write-Host "UI executable ready: $out\loaderspot_ui_$env:ARCH.exe"
|
|
||||||
|
Write-Host "Building CLI for $env:RUST_TARGET"
|
||||||
# build CLI
|
|
||||||
Write-Host "Building CLI for target: $env:RUST_TARGET"
|
|
||||||
cargo build --manifest-path LoaderSpot_CLI/Cargo.toml --release --target $env:RUST_TARGET
|
cargo build --manifest-path LoaderSpot_CLI/Cargo.toml --release --target $env:RUST_TARGET
|
||||||
$exePathCLI = Join-Path -Path "LoaderSpot_CLI" -ChildPath (Join-Path -Path "target" -ChildPath (Join-Path -Path $env:RUST_TARGET -ChildPath "release"))
|
$exeCLI = Get-ChildItem "LoaderSpot_CLI/target/$env:RUST_TARGET/release/*.exe" | Select-Object -First 1
|
||||||
$exeCLI = Get-ChildItem -Path $exePathCLI -Filter "*.exe" -File | Select-Object -First 1
|
Copy-Item $exeCLI.FullName "$out\loaderspot-cli-win-$env:ARCH.exe"
|
||||||
Copy-Item $exeCLI.FullName -Destination "$out\loaderspot_cli_$env:ARCH.exe" -Force
|
Write-Host "CLI executable ready: loaderspot-cli-win-$env:ARCH.exe"
|
||||||
Write-Host "CLI executable ready: $out\loaderspot_cli_$env:ARCH.exe"
|
|
||||||
|
|
||||||
- name: Upload Windows artifacts
|
- name: Upload Windows artifacts
|
||||||
uses: actions/upload-artifact@v5
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: windows-artifacts-${{ matrix.arch }}
|
name: windows-artifacts-${{ matrix.arch }}
|
||||||
path: |
|
path: artifacts/*
|
||||||
artifacts/loaderspot_ui_${{ matrix.arch }}.exe
|
|
||||||
artifacts/loaderspot_cli_${{ matrix.arch }}.exe
|
|
||||||
|
|
||||||
build_linux:
|
build_linux:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- arch: x64
|
|
||||||
rust_target: x86_64-unknown-linux-gnu
|
|
||||||
- arch: arm64
|
|
||||||
rust_target: aarch64-unknown-linux-gnu
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Install Rust toolchain and target
|
- name: Install Rust toolchain
|
||||||
run: |
|
uses: dtolnay/rust-toolchain@stable
|
||||||
rustup toolchain install stable --profile minimal
|
with:
|
||||||
rustup target add ${{ matrix.rust_target }}
|
targets: x86_64-unknown-linux-gnu
|
||||||
rustup default stable
|
|
||||||
|
- name: Cache cargo dependencies
|
||||||
|
uses: actions/cache@v5
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
LoaderSpot_UI/target
|
||||||
|
LoaderSpot_CLI/target
|
||||||
|
key: ${{ runner.os }}-cargo-x86_64-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-cargo-x86_64-
|
||||||
|
|
||||||
- name: Install cross-compilation tools
|
- name: Install build dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
if [ "${{ matrix.rust_target }}" = "aarch64-unknown-linux-gnu" ]; then
|
sudo apt-get install -y pkg-config libssl-dev
|
||||||
sudo apt-get install -y gcc-aarch64-linux-gnu
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Build release binary
|
- name: Build release binaries
|
||||||
env:
|
|
||||||
RUST_TARGET: ${{ matrix.rust_target }}
|
|
||||||
ARCH: ${{ matrix.arch }}
|
|
||||||
run: |
|
run: |
|
||||||
# Set linker for cross-compilation
|
|
||||||
if [ "$RUST_TARGET" = "aarch64-unknown-linux-gnu" ]; then
|
|
||||||
export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p artifacts
|
mkdir -p artifacts
|
||||||
|
|
||||||
# build CLI
|
# Build UI for Linux
|
||||||
echo "Building CLI for target: $RUST_TARGET"
|
echo "Building UI for x86_64-unknown-linux-gnu"
|
||||||
cargo build --manifest-path LoaderSpot_CLI/Cargo.toml --release --target $RUST_TARGET
|
cargo build --manifest-path LoaderSpot_UI/Cargo.toml --release --target x86_64-unknown-linux-gnu
|
||||||
cp "LoaderSpot_CLI/target/$RUST_TARGET/release/loaderspot_cli" "artifacts/loaderspot_cli_linux_$ARCH"
|
cp "LoaderSpot_UI/target/x86_64-unknown-linux-gnu/release/loaderspot_ui" "artifacts/loaderspot-ui-linux-x64"
|
||||||
echo "CLI executable ready: artifacts/loaderspot_cli_linux_$ARCH"
|
echo "UI executable ready: loaderspot-ui-linux-x64"
|
||||||
|
|
||||||
|
# Build CLI for Linux
|
||||||
|
echo "Building CLI for x86_64-unknown-linux-gnu"
|
||||||
|
cargo build --manifest-path LoaderSpot_CLI/Cargo.toml --release --target x86_64-unknown-linux-gnu
|
||||||
|
cp "LoaderSpot_CLI/target/x86_64-unknown-linux-gnu/release/loaderspot_cli" "artifacts/loaderspot-cli-linux-x64"
|
||||||
|
echo "CLI executable ready: loaderspot-cli-linux-x64"
|
||||||
|
|
||||||
- name: Upload Linux artifact
|
- name: Upload Linux artifacts
|
||||||
uses: actions/upload-artifact@v5
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: linux-artifacts-${{ matrix.arch }}
|
name: linux-artifacts-x64
|
||||||
path: artifacts/loaderspot_cli_linux_${{ matrix.arch }}
|
path: artifacts/*
|
||||||
|
|
||||||
release:
|
release:
|
||||||
needs: [build_windows, build_linux]
|
needs: [build_windows, build_linux]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: Download all artifacts
|
- name: Download all artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
path: artifacts
|
path: artifacts
|
||||||
|
|
||||||
|
|
@ -127,14 +131,15 @@ jobs:
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v8
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const releases = await github.rest.repos.listReleases({
|
try {
|
||||||
owner: context.repo.owner,
|
const release = await github.rest.repos.getLatestRelease({
|
||||||
repo: context.repo.repo
|
owner: context.repo.owner,
|
||||||
});
|
repo: context.repo.repo
|
||||||
if (!releases.data || releases.data.length === 0) {
|
});
|
||||||
core.setFailed('No releases found for repository');
|
return release.data.id;
|
||||||
|
} catch (error) {
|
||||||
|
core.setFailed(`No releases found: ${error.message}`);
|
||||||
}
|
}
|
||||||
return releases.data[0].id;
|
|
||||||
|
|
||||||
- name: Upload assets to Release
|
- name: Upload assets to Release
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v8
|
||||||
|
|
@ -164,7 +169,7 @@ jobs:
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Uploading new asset: ${fileName}`);
|
console.log(`Uploading: ${fileName}`);
|
||||||
await github.rest.repos.uploadReleaseAsset({
|
await github.rest.repos.uploadReleaseAsset({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
|
|
@ -174,10 +179,17 @@ jobs:
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const files = [];
|
||||||
const artifactDirs = fs.readdirSync('artifacts');
|
const artifactDirs = fs.readdirSync('artifacts');
|
||||||
for (const dir of artifactDirs) {
|
for (const dir of artifactDirs) {
|
||||||
const files = fs.readdirSync(path.join('artifacts', dir));
|
const dirPath = path.join('artifacts', dir);
|
||||||
for (const file of files) {
|
const dirFiles = fs.readdirSync(dirPath);
|
||||||
await uploadAsset(path.join('artifacts', dir, file));
|
for (const file of dirFiles) {
|
||||||
|
files.push(path.join(dirPath, file));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < files.length; i += 3) {
|
||||||
|
const batch = files.slice(i, i + 3);
|
||||||
|
await Promise.all(batch.map(uploadAsset));
|
||||||
|
}
|
||||||
2
.github/workflows/check-deb-versions.yml
vendored
2
.github/workflows/check-deb-versions.yml
vendored
|
|
@ -13,7 +13,7 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PERSONAL_TOKEN }}
|
token: ${{ secrets.PERSONAL_TOKEN }}
|
||||||
|
|
||||||
|
|
|
||||||
4
.github/workflows/ms-spotify.yml
vendored
4
.github/workflows/ms-spotify.yml
vendored
|
|
@ -1,8 +1,6 @@
|
||||||
name: check Spotify MS
|
name: check Spotify MS
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
|
||||||
- cron: '0 */5 * * *'
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
@ -11,7 +9,7 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: run ms-spotify-check.ps1
|
- name: run ms-spotify-check.ps1
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
|
|
||||||
20
.github/workflows/sync-files.yml
vendored
20
.github/workflows/sync-files.yml
vendored
|
|
@ -1,13 +1,15 @@
|
||||||
name: Sync files to table
|
name: Sync files to table
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
# Temporarily disabled automatic sync trigger.
|
||||||
branches:
|
# Uncomment this block to restore automatic runs on push to main.
|
||||||
- main
|
# push:
|
||||||
paths:
|
# branches:
|
||||||
- 'versions.json'
|
# - main
|
||||||
- 'versions_deb.json'
|
# paths:
|
||||||
workflow_dispatch:
|
# - 'versions.json'
|
||||||
|
# - 'versions_deb.json'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
sync-files:
|
sync-files:
|
||||||
|
|
@ -17,10 +19,10 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout source repo
|
- name: Checkout source repo
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Checkout target repo
|
- name: Checkout target repo
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
repository: LoaderSpot/table
|
repository: LoaderSpot/table
|
||||||
path: table
|
path: table
|
||||||
|
|
|
||||||
11
.github/workflows/update_versions.yml
vendored
11
.github/workflows/update_versions.yml
vendored
|
|
@ -8,7 +8,11 @@ on:
|
||||||
required: true
|
required: true
|
||||||
version:
|
version:
|
||||||
description: 'Another version parameter'
|
description: 'Another version parameter'
|
||||||
required: true
|
required: true
|
||||||
|
buildType:
|
||||||
|
description: 'Build type (e.g., Master, Release)'
|
||||||
|
required: false
|
||||||
|
default: 'false'
|
||||||
jobs:
|
jobs:
|
||||||
update-version:
|
update-version:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
@ -17,7 +21,7 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PERSONAL_TOKEN }}
|
token: ${{ secrets.PERSONAL_TOKEN }}
|
||||||
|
|
||||||
|
|
@ -29,7 +33,8 @@ jobs:
|
||||||
- name: Update version in versions.json
|
- name: Update version in versions.json
|
||||||
run: |
|
run: |
|
||||||
data="${{ github.event.inputs.datajson }}"
|
data="${{ github.event.inputs.datajson }}"
|
||||||
python3 -c "import json, re; data=json.loads('$data'); file_data=json.load(open('versions.json')); file_data[list(data.keys())[0]] = data[list(data.keys())[0]]; file_data = dict(sorted(file_data.items(), key=lambda x: [int(part) if part.isdigit() else part for part in re.split(r'(\d+)', x[0])], reverse=True)); json.dump(file_data, open('versions.json', 'w'), indent=2)"
|
build_type="${{ github.event.inputs.buildType }}"
|
||||||
|
python3 update_version.py "$data" "$build_type"
|
||||||
|
|
||||||
- name: Commit changes
|
- name: Commit changes
|
||||||
run: |
|
run: |
|
||||||
|
|
|
||||||
58
.github/workflows/version-check.yml
vendored
58
.github/workflows/version-check.yml
vendored
|
|
@ -8,29 +8,47 @@ concurrency:
|
||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
version-search:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
versions: ${{ steps.search.outputs.versions }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
- name: Download latest loaderspot_cli_linux_x64
|
||||||
uses: dtolnay/rust-toolchain@stable
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
- name: Cache Cargo dependencies
|
run: |
|
||||||
uses: actions/cache@v4
|
gh release download --repo ${{ github.repository }} --pattern "loaderspot-cli-linux-x64" --output loaderspot_cli
|
||||||
with:
|
chmod +x loaderspot_cli
|
||||||
path: |
|
|
||||||
~/.cargo/bin/
|
|
||||||
~/.cargo/registry/index/
|
|
||||||
~/.cargo/registry/cache/
|
|
||||||
~/.cargo/git/db/
|
|
||||||
LoaderSpot_CLI/target/
|
|
||||||
key: ${{ runner.os }}-cargo-${{ hashFiles('LoaderSpot_CLI/Cargo.lock') }}
|
|
||||||
|
|
||||||
- name: Build LoaderSpot_CLI
|
|
||||||
run: cd LoaderSpot_CLI && cargo build --release
|
|
||||||
|
|
||||||
- name: Version search
|
- name: Version search
|
||||||
run: ${{ github.workspace }}/LoaderSpot_CLI/target/release/loaderspot_cli --version "${{ github.event.client_payload.v }}" --source "${{ github.event.client_payload.s }}" --gas-url "${{ secrets.GOOGLE_APPS_URL }}" --connections 300
|
id: search
|
||||||
|
if: github.event.client_payload.v && github.event.client_payload.s
|
||||||
|
env:
|
||||||
|
v: ${{ github.event.client_payload.v }}
|
||||||
|
s: ${{ github.event.client_payload.s }}
|
||||||
|
run: |
|
||||||
|
{
|
||||||
|
echo 'versions<<EOF'
|
||||||
|
./loaderspot_cli --version "$v" --connections 300 --ladder-search
|
||||||
|
echo 'EOF'
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
get-build-and-send:
|
||||||
|
runs-on: windows-latest
|
||||||
|
needs: version-search
|
||||||
|
if: success() && needs.version-search.outputs.versions
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Get build info and send to GAS
|
||||||
|
shell: pwsh
|
||||||
|
env:
|
||||||
|
GOOGLE_APPS_URL: ${{ secrets.GOOGLE_APPS_URL }}
|
||||||
|
VERSIONS: ${{ needs.version-search.outputs.versions }}
|
||||||
|
SOURCE: ${{ github.event.client_payload.s }}
|
||||||
|
run: |
|
||||||
|
./check-build-version.ps1 -versions "$env:VERSIONS" -source "$env:SOURCE" -googleAppsUrl "$env:GOOGLE_APPS_URL"
|
||||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/LoaderSpot_CLI/target
|
||||||
|
/LoaderSpot_UI/target
|
||||||
|
|
@ -7,7 +7,6 @@ use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::sync::Semaphore;
|
use tokio::sync::Semaphore;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use scraper::{Html, Selector};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
|
||||||
enum PlatformArch {
|
enum PlatformArch {
|
||||||
|
|
@ -149,13 +148,9 @@ struct Cli {
|
||||||
#[clap(long, default_value_t = 100)]
|
#[clap(long, default_value_t = 100)]
|
||||||
connections: usize,
|
connections: usize,
|
||||||
|
|
||||||
/// URL to send the found versions to (Google Apps Script)
|
/// Use ladder search algorithm
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
gas_url: Option<String>,
|
ladder_search: bool,
|
||||||
|
|
||||||
/// Source of the version
|
|
||||||
#[clap(long)]
|
|
||||||
source: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_range(range_str: &str) -> (i32, i32) {
|
fn parse_range(range_str: &str) -> (i32, i32) {
|
||||||
|
|
@ -220,7 +215,7 @@ async fn main() {
|
||||||
let connections = cli.connections.clamp(50, 300);
|
let connections = cli.connections.clamp(50, 300);
|
||||||
let range = cli.range.clone();
|
let range = cli.range.clone();
|
||||||
let client = Client::builder()
|
let client = Client::builder()
|
||||||
.timeout(std::time::Duration::from_secs(10))
|
.timeout(std::time::Duration::from_secs(30))
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
@ -231,43 +226,42 @@ async fn main() {
|
||||||
pb.set_style(spinner_style);
|
pb.set_style(spinner_style);
|
||||||
pb.enable_steady_tick(Duration::from_millis(80));
|
pb.enable_steady_tick(Duration::from_millis(80));
|
||||||
|
|
||||||
let version_clone = cli.version.clone();
|
let versions = cli.version.clone();
|
||||||
let client_clone = client.clone();
|
let client_clone = client.clone();
|
||||||
let gas_url_clone = cli.gas_url.clone();
|
let ladder_search = cli.ladder_search;
|
||||||
let source_clone = cli.source.clone();
|
let versions_clone_for_task = versions.clone();
|
||||||
let search_task = tokio::spawn(async move {
|
|
||||||
let mut all_found_urls = Vec::new();
|
|
||||||
|
|
||||||
for version in &version_clone {
|
let search_task = tokio::spawn(async move {
|
||||||
|
let mut results = Vec::new();
|
||||||
|
|
||||||
|
for version in &versions_clone_for_task {
|
||||||
|
let mut all_found_urls_for_version = Vec::new();
|
||||||
let mut arches_to_search = platform_arches.clone();
|
let mut arches_to_search = platform_arches.clone();
|
||||||
|
|
||||||
if !should_use_win_x86(version) && arches_to_search.contains(&PlatformArch::WinX86) {
|
if !should_use_win_x86(version) && arches_to_search.contains(&PlatformArch::WinX86) {
|
||||||
if version_clone.len() == 1 && arches_to_search.len() == 1 {
|
if versions_clone_for_task.len() == 1 && arches_to_search.len() == 1 {
|
||||||
eprintln!("Warning: x86 architecture for Windows is no longer supported for versions newer than 1.2.53.");
|
eprintln!("Warning: x86 architecture for Windows is no longer supported for versions newer than 1.2.53.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
arches_to_search.retain(|&p| p != PlatformArch::WinX86);
|
arches_to_search.retain(|&p| p != PlatformArch::WinX86);
|
||||||
}
|
}
|
||||||
|
|
||||||
if gas_url_clone.is_some() && source_clone.is_some() {
|
if ladder_search {
|
||||||
// "Лесенка"
|
|
||||||
let mut start_number = 0;
|
let mut start_number = 0;
|
||||||
let mut before_enter = 1000;
|
let mut before_enter = 1000;
|
||||||
let additional_searches = 15;
|
let additional_searches = 15;
|
||||||
let increment = 1000;
|
let increment = 1000;
|
||||||
|
|
||||||
// Первый проход
|
|
||||||
for &platform_arch in &arches_to_search {
|
for &platform_arch in &arches_to_search {
|
||||||
let found = search_installers(&client_clone, version, start_number, before_enter, platform_arch, connections).await;
|
let found = search_installers(&client_clone, version, start_number, before_enter, platform_arch, connections).await;
|
||||||
all_found_urls.extend(found);
|
all_found_urls_for_version.extend(found);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Дополнительные поиски
|
|
||||||
for _ in 0..additional_searches {
|
for _ in 0..additional_searches {
|
||||||
let latest_urls = get_latest_urls(&all_found_urls, &[version.to_string()], source_clone.as_deref().unwrap_or(""));
|
let latest_urls = get_latest_urls(&all_found_urls_for_version);
|
||||||
let target_len = arches_to_search.iter().filter(|&&p| p != PlatformArch::WinX86 || should_use_win_x86(version)).count();
|
let target_len = arches_to_search.iter().filter(|&&p| p != PlatformArch::WinX86 || should_use_win_x86(version)).count();
|
||||||
|
|
||||||
if latest_urls.len() >= target_len + 2 { // +2 for version and source
|
if latest_urls.len() >= target_len {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -280,55 +274,40 @@ async fn main() {
|
||||||
missing_arches.push(platform_arch);
|
missing_arches.push(platform_arch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for &platform_arch in &missing_arches {
|
for &platform_arch in &missing_arches {
|
||||||
let found = search_installers(&client_clone, version, start_number, before_enter, platform_arch, connections).await;
|
let found = search_installers(&client_clone, version, start_number, before_enter, platform_arch, connections).await;
|
||||||
all_found_urls.extend(found);
|
all_found_urls_for_version.extend(found);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Стандартный поиск
|
|
||||||
let (start, end) = parse_range(&range);
|
let (start, end) = parse_range(&range);
|
||||||
for &platform_arch in &arches_to_search {
|
for &platform_arch in &arches_to_search {
|
||||||
let found = search_installers(&client_clone, version, start, end, platform_arch, connections).await;
|
let found = search_installers(&client_clone, version, start, end, platform_arch, connections).await;
|
||||||
all_found_urls.extend(found);
|
all_found_urls_for_version.extend(found);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut latest_urls = get_latest_urls(&all_found_urls_for_version);
|
||||||
|
latest_urls.insert("version".to_string(), version.clone());
|
||||||
|
results.push(latest_urls);
|
||||||
}
|
}
|
||||||
all_found_urls
|
results
|
||||||
});
|
});
|
||||||
|
|
||||||
let all_found_urls = search_task.await.unwrap();
|
let results = search_task.await.unwrap();
|
||||||
|
|
||||||
pb.finish_and_clear();
|
pb.finish_and_clear();
|
||||||
|
|
||||||
match (&cli.gas_url, &cli.source) {
|
if versions.len() == 1 {
|
||||||
(Some(gas_url), Some(source)) => {
|
let json_output = serde_json::to_string_pretty(&results[0]).unwrap();
|
||||||
let latest_urls = get_latest_urls(&all_found_urls, &cli.version, source);
|
println!("{}", json_output);
|
||||||
send_to_gas(client, gas_url, latest_urls).await;
|
} else {
|
||||||
}
|
let json_output = serde_json::to_string_pretty(&results).unwrap();
|
||||||
(Some(_), None) => {
|
println!("{}", json_output);
|
||||||
eprintln!("Warning: --gas-url is provided, but --source is missing. Skipping sending to GAS.");
|
|
||||||
let json_output = serde_json::to_string_pretty(&all_found_urls).unwrap();
|
|
||||||
println!("{}", json_output);
|
|
||||||
}
|
|
||||||
(None, Some(_)) => {
|
|
||||||
eprintln!("Warning: --source is provided, but --gas-url is missing. Skipping sending to GAS.");
|
|
||||||
let json_output = serde_json::to_string_pretty(&all_found_urls).unwrap();
|
|
||||||
println!("{}", json_output);
|
|
||||||
}
|
|
||||||
(None, None) => {
|
|
||||||
let json_output = serde_json::to_string_pretty(&all_found_urls).unwrap();
|
|
||||||
println!("{}", json_output);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_latest_urls(
|
fn get_latest_urls(found_urls: &[(String, PlatformArch)]) -> HashMap<String, String> {
|
||||||
found_urls: &[(String, PlatformArch)],
|
|
||||||
versions: &[String],
|
|
||||||
source: &str,
|
|
||||||
) -> HashMap<String, String> {
|
|
||||||
let mut platform_urls = HashMap::new();
|
let mut platform_urls = HashMap::new();
|
||||||
let version_pattern = Regex::new(r"-(\d+)\.(exe|tbz)$").unwrap();
|
let version_pattern = Regex::new(r"-(\d+)\.(exe|tbz)$").unwrap();
|
||||||
|
|
||||||
|
|
@ -347,62 +326,16 @@ fn get_latest_urls(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut latest_urls: HashMap<String, String> = platform_urls
|
let latest_urls: HashMap<String, String> = platform_urls
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(k, (v, _))| (k, v))
|
.map(|(k, (v, _))| (k, v))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if !versions.is_empty() {
|
|
||||||
latest_urls.insert("version".to_string(), versions.join(", "));
|
|
||||||
}
|
|
||||||
|
|
||||||
latest_urls.insert("source".to_string(), source.to_string());
|
|
||||||
|
|
||||||
|
|
||||||
if latest_urls.is_empty() {
|
if latest_urls.is_empty() {
|
||||||
let mut empty_map = HashMap::new();
|
let mut empty_map = HashMap::new();
|
||||||
empty_map.insert("unknown".to_string(), "unknown".to_string());
|
empty_map.insert("unknown".to_string(), "unknown".to_string());
|
||||||
if !versions.is_empty() {
|
|
||||||
empty_map.insert("version".to_string(), versions.join(", "));
|
|
||||||
}
|
|
||||||
empty_map.insert("source".to_string(), source.to_string());
|
|
||||||
return empty_map;
|
return empty_map;
|
||||||
}
|
}
|
||||||
|
|
||||||
latest_urls
|
latest_urls
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_to_gas(
|
|
||||||
client: Client,
|
|
||||||
gas_url: &str,
|
|
||||||
data: HashMap<String, String>,
|
|
||||||
) {
|
|
||||||
let cleaned_gas_url = gas_url.trim_matches('"');
|
|
||||||
|
|
||||||
match client.post(cleaned_gas_url).json(&data).send().await {
|
|
||||||
Ok(response) => {
|
|
||||||
if response.status().is_success() {
|
|
||||||
match response.text().await {
|
|
||||||
Ok(text) => {
|
|
||||||
if text.contains("<div") {
|
|
||||||
let soup = Html::parse_document(&text);
|
|
||||||
let selector = Selector::parse("div[style*='text-align:center']").unwrap();
|
|
||||||
if let Some(div) = soup.select(&selector).next() {
|
|
||||||
println!("Ответ от GAS: {}", div.text().collect::<String>().trim());
|
|
||||||
} else {
|
|
||||||
eprintln!("Не удалось извлечь ответ из HTML. Полный ответ:");
|
|
||||||
eprintln!("{}", text);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
println!("Ответ от GAS: {}", text.trim());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => eprintln!("Ошибка чтения ответа от GAS: {}", e),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
eprintln!("Ошибка при отправке в GAS: {}", response.status());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => eprintln!("Ошибка при отправке запроса в GAS: {}", e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
38
LoaderSpot_UI/Cargo.lock
generated
38
LoaderSpot_UI/Cargo.lock
generated
|
|
@ -762,6 +762,15 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-channel"
|
||||||
|
version = "0.5.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-utils"
|
name = "crossbeam-utils"
|
||||||
version = "0.8.21"
|
version = "0.8.21"
|
||||||
|
|
@ -1892,6 +1901,21 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
|
checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "loaderspot_ui"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-channel",
|
||||||
|
"eframe",
|
||||||
|
"egui",
|
||||||
|
"regex",
|
||||||
|
"reqwest",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.14"
|
version = "0.4.14"
|
||||||
|
|
@ -3187,20 +3211,6 @@ dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "spotify_finder"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"eframe",
|
|
||||||
"egui",
|
|
||||||
"regex",
|
|
||||||
"reqwest",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"tokio",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stable_deref_trait"
|
name = "stable_deref_trait"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "spotify_finder"
|
name = "loaderspot_ui"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
|
@ -11,4 +11,7 @@ reqwest = { version = "0.12", features = ["json"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
regex = "1.10"
|
regex = "1.10"
|
||||||
winapi = { version = "0.3", features = ["winuser"] }
|
crossbeam-channel = "0.5"
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
winapi = { version = "0.3", features = ["winuser"] }
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
// Hide console window on Windows
|
// Hide console window on Windows
|
||||||
#![cfg_attr(target_os = "windows", windows_subsystem = "windows")]
|
#![cfg_attr(target_os = "windows", windows_subsystem = "windows")]
|
||||||
|
|
||||||
|
use crossbeam_channel::{unbounded, Receiver, Sender};
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
use std::sync::{Arc, OnceLock};
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
|
|
@ -31,13 +31,13 @@ impl Platform {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path_template(&self) -> &str {
|
fn generate_path(&self, version: &str, number: i32) -> String {
|
||||||
match self {
|
match self {
|
||||||
Platform::WinX86 => "win32-x86/spotify_installer-{version}-{number}.exe",
|
Platform::WinX86 => format!("win32-x86/spotify_installer-{version}-{number}.exe"),
|
||||||
Platform::WinX64 => "win32-x86_64/spotify_installer-{version}-{number}.exe",
|
Platform::WinX64 => format!("win32-x86_64/spotify_installer-{version}-{number}.exe"),
|
||||||
Platform::WinArm64 => "win32-arm64/spotify_installer-{version}-{number}.exe",
|
Platform::WinArm64 => format!("win32-arm64/spotify_installer-{version}-{number}.exe"),
|
||||||
Platform::MacOsIntel => "osx-x86_64/spotify-autoupdate-{version}-{number}.tbz",
|
Platform::MacOsIntel => format!("osx-x86_64/spotify-autoupdate-{version}-{number}.tbz"),
|
||||||
Platform::MacOsArm64 => "osx-arm64/spotify-autoupdate-{version}-{number}.tbz",
|
Platform::MacOsArm64 => format!("osx-arm64/spotify-autoupdate-{version}-{number}.tbz"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,11 +63,11 @@ impl UrlGenerator {
|
||||||
const BASE_URL: &'static str = "https://upgrade.scdn.co/upgrade/client/";
|
const BASE_URL: &'static str = "https://upgrade.scdn.co/upgrade/client/";
|
||||||
|
|
||||||
fn generate_url(platform: Platform, version: &str, number: i32) -> String {
|
fn generate_url(platform: Platform, version: &str, number: i32) -> String {
|
||||||
let path = platform
|
format!(
|
||||||
.path_template()
|
"{}{}",
|
||||||
.replace("{version}", version)
|
Self::BASE_URL,
|
||||||
.replace("{number}", &number.to_string());
|
platform.generate_path(version, number)
|
||||||
format!("{}{}", Self::BASE_URL, path)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,15 +83,12 @@ fn extract_base_version(version: &str) -> String {
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn should_use_win_x86(version: &str) -> bool {
|
fn should_use_win_x86(version: &str) -> bool {
|
||||||
let base_version = extract_base_version(version);
|
let mut parts = version.split('.');
|
||||||
let parts: Vec<&str> = base_version.split('.').collect();
|
|
||||||
|
|
||||||
if parts.len() >= 3 {
|
if let (Some(p1), Some(p2), Some(p3)) = (parts.next(), parts.next(), parts.next()) {
|
||||||
if let (Ok(major), Ok(minor), Ok(patch)) = (
|
if let (Ok(major), Ok(minor), Ok(patch)) =
|
||||||
parts[0].parse::<u32>(),
|
(p1.parse::<u32>(), p2.parse::<u32>(), p3.parse::<u32>())
|
||||||
parts[1].parse::<u32>(),
|
{
|
||||||
parts[2].parse::<u32>(),
|
|
||||||
) {
|
|
||||||
return (major, minor, patch) <= (1, 2, 53);
|
return (major, minor, patch) <= (1, 2, 53);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -99,7 +96,8 @@ fn should_use_win_x86(version: &str) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_version(version: &str) -> bool {
|
fn validate_version(version: &str) -> bool {
|
||||||
let re = regex::Regex::new(r"^\d+\.\d+\.\d+\.\d+\.g[0-9a-f]{8}$").unwrap();
|
static RE: OnceLock<regex::Regex> = OnceLock::new();
|
||||||
|
let re = RE.get_or_init(|| regex::Regex::new(r"^\d+\.\d+\.\d+\.\d+\.g[0-9a-f]{8}$").unwrap());
|
||||||
re.is_match(version)
|
re.is_match(version)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,8 +115,23 @@ const MAX_CONNECTION_OPTIONS: [usize; 6] = [50, 100, 150, 200, 250, 300];
|
||||||
|
|
||||||
async fn check_url(client: &Client, url: String, platform: Platform) -> Option<(String, Platform)> {
|
async fn check_url(client: &Client, url: String, platform: Platform) -> Option<(String, Platform)> {
|
||||||
match client.head(&url).send().await {
|
match client.head(&url).send().await {
|
||||||
Ok(response) if response.status().is_success() => Some((url, platform)),
|
Ok(response) => {
|
||||||
_ => None,
|
if response.status().is_success() {
|
||||||
|
Some((url, platform))
|
||||||
|
} else if response.status() == reqwest::StatusCode::TOO_MANY_REQUESTS {
|
||||||
|
eprintln!(
|
||||||
|
"RATE LIMITED (429)! Server blocked the request for: {}",
|
||||||
|
url
|
||||||
|
);
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Network error: {}", e);
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -151,7 +164,7 @@ async fn check_version_and_submit(client: &Client, version: &str) {
|
||||||
|
|
||||||
let version_exists = versions_json
|
let version_exists = versions_json
|
||||||
.values()
|
.values()
|
||||||
.any(|v| v.fullversion.as_ref().map_or(false, |fv| fv == version));
|
.any(|v| v.fullversion.as_ref().is_some_and(|fv| fv == version));
|
||||||
|
|
||||||
if !version_exists {
|
if !version_exists {
|
||||||
submit_to_google_form(client, version).await;
|
submit_to_google_form(client, version).await;
|
||||||
|
|
@ -159,16 +172,15 @@ async fn check_version_and_submit(client: &Client, version: &str) {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SearchMessage {
|
enum SearchMessage {
|
||||||
Progress(u64, u64),
|
|
||||||
Result(String, Platform),
|
Result(String, Platform),
|
||||||
Complete(String),
|
Complete(String),
|
||||||
VersionStart(String, usize, usize),
|
VersionStart(String, usize, usize),
|
||||||
CompleteAll,
|
CompleteAll,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn search_installers(
|
struct SearchOptions {
|
||||||
client: &Client,
|
client: Client,
|
||||||
version: &str,
|
version: String,
|
||||||
start: i32,
|
start: i32,
|
||||||
end: i32,
|
end: i32,
|
||||||
platforms: Vec<Platform>,
|
platforms: Vec<Platform>,
|
||||||
|
|
@ -176,45 +188,59 @@ async fn search_installers(
|
||||||
tx: Sender<SearchMessage>,
|
tx: Sender<SearchMessage>,
|
||||||
pause_flag: Arc<AtomicBool>,
|
pause_flag: Arc<AtomicBool>,
|
||||||
cancel_flag: Arc<AtomicBool>,
|
cancel_flag: Arc<AtomicBool>,
|
||||||
) {
|
processed: Arc<AtomicU64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn search_installers(opts: SearchOptions) {
|
||||||
use tokio::sync::Semaphore;
|
use tokio::sync::Semaphore;
|
||||||
|
|
||||||
let total = ((end - start + 1) * platforms.len() as i32) as u64;
|
let semaphore = Arc::new(Semaphore::new(opts.max_connections));
|
||||||
let semaphore = Arc::new(Semaphore::new(max_connections));
|
|
||||||
let mut tasks = Vec::new();
|
let mut tasks = Vec::new();
|
||||||
let processed = Arc::new(Mutex::new(0u64));
|
|
||||||
|
|
||||||
for platform in platforms {
|
'outer: for platform in opts.platforms {
|
||||||
for number in start..=end {
|
for number in opts.start..=opts.end {
|
||||||
let url = UrlGenerator::generate_url(platform, version, number);
|
if opts.cancel_flag.load(Ordering::Relaxed) {
|
||||||
let client = client.clone();
|
break 'outer;
|
||||||
let permit = semaphore.clone().acquire_owned().await.unwrap();
|
}
|
||||||
let tx_clone = tx.clone();
|
|
||||||
let processed_clone = processed.clone();
|
while opts.pause_flag.load(Ordering::Relaxed) {
|
||||||
let pause_local = pause_flag.clone();
|
if opts.cancel_flag.load(Ordering::Relaxed) {
|
||||||
let cancel_local = cancel_flag.clone();
|
break 'outer;
|
||||||
|
}
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let permit = match semaphore.clone().acquire_owned().await {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(_) => break 'outer,
|
||||||
|
};
|
||||||
|
|
||||||
|
while opts.pause_flag.load(Ordering::Relaxed) {
|
||||||
|
if opts.cancel_flag.load(Ordering::Relaxed) {
|
||||||
|
break 'outer;
|
||||||
|
}
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.cancel_flag.load(Ordering::Relaxed) {
|
||||||
|
break 'outer;
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = UrlGenerator::generate_url(platform, &opts.version, number);
|
||||||
|
let client = opts.client.clone();
|
||||||
|
let tx_clone = opts.tx.clone();
|
||||||
|
let processed_clone = opts.processed.clone();
|
||||||
|
let cancel_local = opts.cancel_flag.clone();
|
||||||
|
|
||||||
let task = tokio::spawn(async move {
|
let task = tokio::spawn(async move {
|
||||||
|
let _permit = permit;
|
||||||
|
|
||||||
if cancel_local.load(Ordering::Relaxed) {
|
if cancel_local.load(Ordering::Relaxed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
while pause_local.load(Ordering::Relaxed) {
|
|
||||||
if cancel_local.load(Ordering::Relaxed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = check_url(&client, url, platform).await;
|
let result = check_url(&client, url, platform).await;
|
||||||
drop(permit);
|
processed_clone.fetch_add(1, Ordering::Relaxed);
|
||||||
|
|
||||||
let mut p = processed_clone.lock().unwrap();
|
|
||||||
*p += 1;
|
|
||||||
let current = *p;
|
|
||||||
drop(p);
|
|
||||||
|
|
||||||
let _ = tx_clone.send(SearchMessage::Progress(current, total));
|
|
||||||
|
|
||||||
if let Some((url, platform)) = result {
|
if let Some((url, platform)) = result {
|
||||||
let _ = tx_clone.send(SearchMessage::Result(url, platform));
|
let _ = tx_clone.send(SearchMessage::Result(url, platform));
|
||||||
|
|
@ -225,11 +251,17 @@ async fn search_installers(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.cancel_flag.load(Ordering::Relaxed) {
|
||||||
|
for task in &tasks {
|
||||||
|
task.abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for task in tasks {
|
for task in tasks {
|
||||||
let _ = task.await;
|
let _ = task.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = tx.send(SearchMessage::Complete(version.to_string()));
|
let _ = opts.tx.send(SearchMessage::Complete(opts.version));
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SpotifyFinderApp {
|
struct SpotifyFinderApp {
|
||||||
|
|
@ -247,7 +279,6 @@ struct SpotifyFinderApp {
|
||||||
|
|
||||||
report_unknown: bool,
|
report_unknown: bool,
|
||||||
|
|
||||||
search_results: String,
|
|
||||||
is_searching: bool,
|
is_searching: bool,
|
||||||
is_paused: bool,
|
is_paused: bool,
|
||||||
displayed_results: String,
|
displayed_results: String,
|
||||||
|
|
@ -262,7 +293,8 @@ struct SpotifyFinderApp {
|
||||||
progress: f32,
|
progress: f32,
|
||||||
progress_text: String,
|
progress_text: String,
|
||||||
total_work: u64,
|
total_work: u64,
|
||||||
processed_global: u64,
|
|
||||||
|
processed_global: Arc<AtomicU64>,
|
||||||
|
|
||||||
rx: Option<Receiver<SearchMessage>>,
|
rx: Option<Receiver<SearchMessage>>,
|
||||||
found_urls: HashMap<Platform, Vec<String>>,
|
found_urls: HashMap<Platform, Vec<String>>,
|
||||||
|
|
@ -284,7 +316,6 @@ impl Default for SpotifyFinderApp {
|
||||||
platform_macos_intel: false,
|
platform_macos_intel: false,
|
||||||
platform_macos_arm64: false,
|
platform_macos_arm64: false,
|
||||||
report_unknown: false,
|
report_unknown: false,
|
||||||
search_results: String::new(),
|
|
||||||
is_searching: false,
|
is_searching: false,
|
||||||
is_paused: false,
|
is_paused: false,
|
||||||
displayed_results: String::new(),
|
displayed_results: String::new(),
|
||||||
|
|
@ -296,7 +327,7 @@ impl Default for SpotifyFinderApp {
|
||||||
progress: 0.0,
|
progress: 0.0,
|
||||||
progress_text: String::new(),
|
progress_text: String::new(),
|
||||||
total_work: 0,
|
total_work: 0,
|
||||||
processed_global: 0,
|
processed_global: Arc::new(AtomicU64::new(0)),
|
||||||
rx: None,
|
rx: None,
|
||||||
found_urls: HashMap::new(),
|
found_urls: HashMap::new(),
|
||||||
pause_flag: Arc::new(AtomicBool::new(false)),
|
pause_flag: Arc::new(AtomicBool::new(false)),
|
||||||
|
|
@ -309,7 +340,6 @@ impl Default for SpotifyFinderApp {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpotifyFinderApp {
|
impl SpotifyFinderApp {
|
||||||
// Improved reveal logic: finish current item immediately and fast-forward the queue
|
|
||||||
fn advance_reveal(&mut self) {
|
fn advance_reveal(&mut self) {
|
||||||
if self.current_reveal.is_some() && !self.reveal_queue.is_empty() {
|
if self.current_reveal.is_some() && !self.reveal_queue.is_empty() {
|
||||||
if let Some(cur) = self.current_reveal.take() {
|
if let Some(cur) = self.current_reveal.take() {
|
||||||
|
|
@ -436,7 +466,6 @@ impl SpotifyFinderApp {
|
||||||
self.is_searching = true;
|
self.is_searching = true;
|
||||||
self.progress = 0.0;
|
self.progress = 0.0;
|
||||||
self.progress_text = "Starting...".to_string();
|
self.progress_text = "Starting...".to_string();
|
||||||
self.search_results.clear();
|
|
||||||
self.displayed_results.clear();
|
self.displayed_results.clear();
|
||||||
self.reveal_queue.clear();
|
self.reveal_queue.clear();
|
||||||
self.current_reveal = None;
|
self.current_reveal = None;
|
||||||
|
|
@ -451,13 +480,15 @@ impl SpotifyFinderApp {
|
||||||
let per_version = ((end - start + 1) * cnt as i32) as u64;
|
let per_version = ((end - start + 1) * cnt as i32) as u64;
|
||||||
total_work_calc = total_work_calc.saturating_add(per_version);
|
total_work_calc = total_work_calc.saturating_add(per_version);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.total_work = total_work_calc;
|
self.total_work = total_work_calc;
|
||||||
self.processed_global = 0;
|
self.processed_global.store(0, Ordering::Relaxed);
|
||||||
|
|
||||||
self.current_version = None;
|
self.current_version = None;
|
||||||
self.current_version_index = 0;
|
self.current_version_index = 0;
|
||||||
self.total_versions = versions.len();
|
self.total_versions = versions.len();
|
||||||
|
|
||||||
let (tx, rx) = channel();
|
let (tx, rx): (Sender<SearchMessage>, Receiver<SearchMessage>) = unbounded();
|
||||||
self.rx = Some(rx);
|
self.rx = Some(rx);
|
||||||
|
|
||||||
self.pause_flag.store(false, Ordering::Relaxed);
|
self.pause_flag.store(false, Ordering::Relaxed);
|
||||||
|
|
@ -469,6 +500,7 @@ impl SpotifyFinderApp {
|
||||||
let pause = self.pause_flag.clone();
|
let pause = self.pause_flag.clone();
|
||||||
let cancel = self.cancel_flag.clone();
|
let cancel = self.cancel_flag.clone();
|
||||||
let base_platforms_for_spawn = base_platforms.clone();
|
let base_platforms_for_spawn = base_platforms.clone();
|
||||||
|
let processed_for_spawn = self.processed_global.clone();
|
||||||
|
|
||||||
self.runtime.spawn(async move {
|
self.runtime.spawn(async move {
|
||||||
let client = Client::builder()
|
let client = Client::builder()
|
||||||
|
|
@ -502,17 +534,18 @@ impl SpotifyFinderApp {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
search_installers(
|
search_installers(SearchOptions {
|
||||||
&client,
|
client: client.clone(),
|
||||||
&version,
|
version,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
platforms_for_version,
|
platforms: platforms_for_version,
|
||||||
max_conn,
|
max_connections: max_conn,
|
||||||
tx.clone(),
|
tx: tx.clone(),
|
||||||
pause.clone(),
|
pause_flag: pause.clone(),
|
||||||
cancel.clone(),
|
cancel_flag: cancel.clone(),
|
||||||
)
|
processed: processed_for_spawn.clone(),
|
||||||
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if cancel.load(Ordering::Relaxed) {
|
if cancel.load(Ordering::Relaxed) {
|
||||||
|
|
@ -534,7 +567,6 @@ impl SpotifyFinderApp {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_results(&mut self) {
|
fn clear_results(&mut self) {
|
||||||
self.search_results.clear();
|
|
||||||
self.displayed_results.clear();
|
self.displayed_results.clear();
|
||||||
self.reveal_queue.clear();
|
self.reveal_queue.clear();
|
||||||
self.current_reveal = None;
|
self.current_reveal = None;
|
||||||
|
|
@ -542,49 +574,49 @@ impl SpotifyFinderApp {
|
||||||
self.progress = 0.0;
|
self.progress = 0.0;
|
||||||
self.progress_text.clear();
|
self.progress_text.clear();
|
||||||
self.total_work = 0;
|
self.total_work = 0;
|
||||||
self.processed_global = 0;
|
self.processed_global.store(0, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_search_progress(&mut self) {
|
fn update_search_progress(&mut self) {
|
||||||
|
let current_processed = self.processed_global.load(Ordering::Relaxed);
|
||||||
|
let denom = if self.total_work == 0 {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
self.total_work
|
||||||
|
};
|
||||||
|
self.progress = current_processed as f32 / denom as f32;
|
||||||
|
|
||||||
|
if let Some(v) = &self.current_version {
|
||||||
|
let short = short_version(v);
|
||||||
|
if self.total_versions > 1 {
|
||||||
|
self.progress_text = format!(
|
||||||
|
"Checking: {}/{}, Version: {}, No. {}/{}",
|
||||||
|
current_processed,
|
||||||
|
denom,
|
||||||
|
short,
|
||||||
|
self.current_version_index,
|
||||||
|
self.total_versions
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
self.progress_text = format!(
|
||||||
|
"Checking: {}/{}, Version: {}",
|
||||||
|
current_processed, denom, short
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if self.is_searching {
|
||||||
|
self.progress_text = format!("Checking: {}/{}", current_processed, denom);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(rx_owned) = self.rx.take() {
|
if let Some(rx_owned) = self.rx.take() {
|
||||||
let rx = rx_owned;
|
|
||||||
let mut completed = false;
|
let mut completed = false;
|
||||||
|
let mut processed_this_frame = 0;
|
||||||
|
|
||||||
|
while let Ok(msg) = rx_owned.try_recv() {
|
||||||
|
processed_this_frame += 1;
|
||||||
|
|
||||||
while let Ok(msg) = rx.try_recv() {
|
|
||||||
match msg {
|
match msg {
|
||||||
SearchMessage::Progress(_current, _total) => {
|
|
||||||
self.processed_global = self.processed_global.saturating_add(1);
|
|
||||||
let denom = if self.total_work == 0 {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
self.total_work
|
|
||||||
};
|
|
||||||
self.progress = self.processed_global as f32 / denom as f32;
|
|
||||||
|
|
||||||
if let Some(v) = &self.current_version {
|
|
||||||
let short = short_version(v);
|
|
||||||
if self.total_versions > 1 {
|
|
||||||
self.progress_text = format!(
|
|
||||||
"Checking: {}/{}, Version: {}, No. {}/{}",
|
|
||||||
self.processed_global,
|
|
||||||
denom,
|
|
||||||
short,
|
|
||||||
self.current_version_index,
|
|
||||||
self.total_versions
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
self.progress_text = format!(
|
|
||||||
"Checking: {}/{}, Version: {}",
|
|
||||||
self.processed_global, denom, short
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.progress_text =
|
|
||||||
format!("Checking: {}/{}", self.processed_global, denom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SearchMessage::Result(url, platform) => {
|
SearchMessage::Result(url, platform) => {
|
||||||
let entry = self.found_urls.entry(platform).or_insert_with(Vec::new);
|
let entry = self.found_urls.entry(platform).or_default();
|
||||||
let first = entry.is_empty();
|
let first = entry.is_empty();
|
||||||
entry.push(url.clone());
|
entry.push(url.clone());
|
||||||
|
|
||||||
|
|
@ -626,10 +658,14 @@ impl SpotifyFinderApp {
|
||||||
completed = true;
|
completed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if processed_this_frame > 1000 && !completed {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !completed {
|
if !completed {
|
||||||
self.rx = Some(rx);
|
self.rx = Some(rx_owned);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -954,15 +990,13 @@ impl eframe::App for SpotifyFinderApp {
|
||||||
self.is_paused = false;
|
self.is_paused = false;
|
||||||
self.pause_flag.store(false, Ordering::Relaxed);
|
self.pause_flag.store(false, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
} else {
|
} else if ui
|
||||||
if ui
|
.add_sized(btn_size, egui::Button::new("⏸ Pause"))
|
||||||
.add_sized(btn_size, egui::Button::new("⏸ Pause"))
|
.clicked()
|
||||||
.clicked()
|
{
|
||||||
{
|
self.is_paused = true;
|
||||||
self.is_paused = true;
|
self.pause_flag.store(true, Ordering::Relaxed);
|
||||||
self.pause_flag.store(true, Ordering::Relaxed);
|
self.progress_text = "Paused".to_string();
|
||||||
self.progress_text = "Paused".to_string();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ui
|
if ui
|
||||||
|
|
@ -971,13 +1005,11 @@ impl eframe::App for SpotifyFinderApp {
|
||||||
{
|
{
|
||||||
self.stop_search();
|
self.stop_search();
|
||||||
}
|
}
|
||||||
} else {
|
} else if ui
|
||||||
if ui
|
.add_sized(btn_size, egui::Button::new("▶ Start Search"))
|
||||||
.add_sized(btn_size, egui::Button::new("▶ Start Search"))
|
.clicked()
|
||||||
.clicked()
|
{
|
||||||
{
|
self.start_search();
|
||||||
self.start_search();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.add_enabled_ui(!self.is_searching, |ui| {
|
ui.add_enabled_ui(!self.is_searching, |ui| {
|
||||||
|
|
|
||||||
125
check-build-version.ps1
Normal file
125
check-build-version.ps1
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
param (
|
||||||
|
[string]$versions,
|
||||||
|
[string]$source,
|
||||||
|
[string]$googleAppsUrl
|
||||||
|
)
|
||||||
|
|
||||||
|
function Find-BuildInfo {
|
||||||
|
[CmdletBinding()]
|
||||||
|
param (
|
||||||
|
[Parameter(Mandatory=$true, ValueFromPipeline=$true, Position=0)]
|
||||||
|
[string]$Path
|
||||||
|
)
|
||||||
|
|
||||||
|
process {
|
||||||
|
try {
|
||||||
|
$fileContent = Get-Content -Path $Path -ErrorAction Stop
|
||||||
|
$found = $false
|
||||||
|
|
||||||
|
$regex = '(Master|Release|PR|Local) Build.+(?:cef_)?(\d+\.\d+\.\d+\+g[0-9a-f]+\+chromium-\d+\.\d+\.\d+\.\d+)'
|
||||||
|
|
||||||
|
foreach ($line in $fileContent) {
|
||||||
|
$match = [regex]::Match($line, $regex)
|
||||||
|
if ($match.Success) {
|
||||||
|
$buildType = $match.Groups[1].Value
|
||||||
|
Write-Host "build type: $buildType"
|
||||||
|
return $buildType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $found) {
|
||||||
|
Write-Host "Билд не найден"
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Error "Ошибка при чтении файла: $_"
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-BuildTypeFromUrl {
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[string]$Url
|
||||||
|
)
|
||||||
|
|
||||||
|
$tempPath = Join-Path ([System.IO.Path]::GetTempPath()) "spotify"
|
||||||
|
$destinationPath = Join-Path $tempPath "unpacked"
|
||||||
|
$exePath = Join-Path $tempPath "SpotifySetup.exe"
|
||||||
|
|
||||||
|
if (-not (Test-Path -Path $tempPath)) {
|
||||||
|
New-Item -ItemType Directory -Path $tempPath | Out-Null
|
||||||
|
}
|
||||||
|
if (-not (Test-Path -Path $destinationPath)) {
|
||||||
|
New-Item -ItemType Directory -Path $destinationPath | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Write-Host "Скачивание файла из $Url..."
|
||||||
|
Invoke-WebRequest -Uri $Url -OutFile $exePath
|
||||||
|
Write-Host "Файл сохранен в $exePath"
|
||||||
|
|
||||||
|
Write-Host "Распаковка файла в $destinationPath"
|
||||||
|
Start-Process -Wait -FilePath $exePath -ArgumentList "/extract `"$destinationPath`""
|
||||||
|
|
||||||
|
$dllPath = Join-Path $destinationPath "Spotify.dll"
|
||||||
|
$exePathForAnalysis = Join-Path $destinationPath "Spotify.exe"
|
||||||
|
|
||||||
|
if (Test-Path $dllPath) {
|
||||||
|
return Find-BuildInfo -Path $dllPath
|
||||||
|
} elseif (Test-Path $exePathForAnalysis) {
|
||||||
|
return Find-BuildInfo -Path $exePathForAnalysis
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Error "Файлы Spotify.dll и Spotify.exe не найдены в $destinationPath"
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Error "Произошла ошибка при скачивании или распаковке: $_"
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([string]::IsNullOrEmpty($versions) -or [string]::IsNullOrEmpty($source) -or [string]::IsNullOrEmpty($googleAppsUrl)) {
|
||||||
|
Write-Error "Один или несколько обязательных параметров (versions, source, googleAppsUrl) не предоставлены."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$versionsObj = $versions | ConvertFrom-Json
|
||||||
|
$win64Url = $versionsObj.WIN64
|
||||||
|
$buildType = $false
|
||||||
|
|
||||||
|
if ($win64Url) {
|
||||||
|
$buildType = Get-BuildTypeFromUrl -Url $win64Url
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($buildType -eq $false) {
|
||||||
|
$versionsObj | Add-Member -NotePropertyName "buildType" -NotePropertyValue $false -Force
|
||||||
|
} else {
|
||||||
|
$versionsObj | Add-Member -NotePropertyName "buildType" -NotePropertyValue $buildType -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
$versionsObj | Add-Member -NotePropertyName "source" -NotePropertyValue $source -Force
|
||||||
|
|
||||||
|
$finalJson = $versionsObj | ConvertTo-Json -Compress
|
||||||
|
|
||||||
|
Write-Host "Отправка данных на GAS..."
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = Invoke-WebRequest -Uri $googleAppsUrl `
|
||||||
|
-Method POST `
|
||||||
|
-ContentType "application/json" `
|
||||||
|
-Body $finalJson `
|
||||||
|
-UseBasicParsing -ErrorAction Stop
|
||||||
|
|
||||||
|
if ($response.StatusCode -eq 200) {
|
||||||
|
Write-Host "Ответ от GAS: $($response.Content)"
|
||||||
|
} else {
|
||||||
|
Write-Error "Ошибка при отправке в GAS. Статус: $($response.StatusCode). Ответ: $($response.Content)"
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Error "Критическая ошибка при отправке в GAS: $_"
|
||||||
|
}
|
||||||
|
|
@ -123,9 +123,9 @@ def commit_changes(new_versions):
|
||||||
# Формируем сообщение коммита в зависимости от количества новых версий
|
# Формируем сообщение коммита в зависимости от количества новых версий
|
||||||
if new_versions:
|
if new_versions:
|
||||||
commit_message = (
|
commit_message = (
|
||||||
f"Added deb version {new_versions[0][0]}"
|
f"Added deb version {new_versions[0][1]}"
|
||||||
if len(new_versions) == 1
|
if len(new_versions) == 1
|
||||||
else f"Added deb versions: {', '.join(v[0] for v in new_versions)}"
|
else f"Added deb versions: {', '.join(v[1] for v in new_versions)}"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
commit_message = "Update Spotify versions"
|
commit_message = "Update Spotify versions"
|
||||||
|
|
|
||||||
63
update_version.py
Normal file
63
update_version.py
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def update_version(data_json: str, build_type: str = None, version_file: str = "versions.json") -> bool:
|
||||||
|
try:
|
||||||
|
data = json.loads(data_json)
|
||||||
|
version_key = list(data.keys())[0]
|
||||||
|
version_data = data[version_key]
|
||||||
|
|
||||||
|
if build_type and build_type.lower() != 'false':
|
||||||
|
version_data = {'buildType': build_type, **version_data}
|
||||||
|
|
||||||
|
with open(version_file, 'r', encoding='utf-8') as f:
|
||||||
|
file_data = json.load(f)
|
||||||
|
|
||||||
|
file_data[version_key] = version_data
|
||||||
|
|
||||||
|
sorted_data = dict(
|
||||||
|
sorted(
|
||||||
|
file_data.items(),
|
||||||
|
key=lambda x: [
|
||||||
|
int(part) if part.isdigit() else part
|
||||||
|
for part in re.split(r'(\d+)', x[0])
|
||||||
|
],
|
||||||
|
reverse=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
with open(version_file, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(sorted_data, f, indent=2)
|
||||||
|
|
||||||
|
print(f"Version {version_key} added to {version_file}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
print(f"JSON parse error: {e}", file=sys.stderr)
|
||||||
|
return False
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"File {version_file} not found", file=sys.stderr)
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: {e}", file=sys.stderr)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("Usage: python update_version.py '<json_data>' [build_type] [version_file]", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
data_json = sys.argv[1]
|
||||||
|
build_type = sys.argv[2] if len(sys.argv) > 2 else None
|
||||||
|
version_file = sys.argv[3] if len(sys.argv) > 3 else "versions.json"
|
||||||
|
|
||||||
|
success = update_version(data_json, build_type, version_file)
|
||||||
|
sys.exit(0 if success else 1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
1778
versions.json
1778
versions.json
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,44 @@
|
||||||
{
|
{
|
||||||
|
"1.2.84.476": {
|
||||||
|
"fullversion": "1.2.84.476.ga1ff6607",
|
||||||
|
"size": "157643240",
|
||||||
|
"data": "09.03.2026",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.84.476.ga1ff6607_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.2.82.428": {
|
||||||
|
"fullversion": "1.2.82.428.g0ac8be2b",
|
||||||
|
"size": "156375346",
|
||||||
|
"data": "09.02.2026",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.82.428.g0ac8be2b_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.2.79.427": {
|
||||||
|
"fullversion": "1.2.79.427.g80eb4a07",
|
||||||
|
"size": "155456648",
|
||||||
|
"data": "12.01.2026",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.79.427.g80eb4a07_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.2.79.425": {
|
||||||
|
"fullversion": "1.2.79.425.g1d0fcf61",
|
||||||
|
"size": "155381166",
|
||||||
|
"data": "17.12.2025",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.79.425.g1d0fcf61_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.2.77.358": {
|
||||||
|
"fullversion": "1.2.77.358.g4339a634",
|
||||||
|
"size": "155510286",
|
||||||
|
"data": "25.11.2025",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.77.358.g4339a634_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"1.2.74.477": {
|
"1.2.74.477": {
|
||||||
"fullversion": "1.2.74.477.g3be53afe",
|
"fullversion": "1.2.74.477.g3be53afe",
|
||||||
"size": "154653044",
|
"size": "154653044",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue