mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
refactor: remove Tauri auto-updater, replace with simple GitHub-check modal
Remove tauri-plugin-updater, relaunch_after_update command, generate-update- manifest.js, the generate-manifest CI job, and all signing/upload steps for .sig files and latest.json. The updater plugin, its pubkey and endpoint config in tauri.conf.json, and the updater:default capability are all gone. Replace AppUpdater.tsx with a lightweight component that: - Fetches https://api.github.com/repos/Psychotoxical/psysonic/releases/latest after a 4 s delay (no install, no download, no progress bar) - Shows a dismissible toast with two buttons: GitHub → releases/latest Website → https://psysonic.psychotoxic.eu/#downloads i18n: remove updaterInstall/Downloading/Installing/Download/ExperimentalHint keys from all 7 locales; add updaterWebsite; update updaterVersion wording. CI: remove generate-manifest job and all .sig signing/upload steps from build-macos-windows. Also remove TAURI_SIGNING_PRIVATE_KEY env vars (no longer needed). Release now just builds and uploads the installable binaries. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -80,9 +80,6 @@ jobs:
|
|||||||
needs: create-release
|
needs: create-release
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
env:
|
|
||||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
|
||||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -124,42 +121,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
releaseId: ${{ needs.create-release.outputs.release_id }}
|
releaseId: ${{ needs.create-release.outputs.release_id }}
|
||||||
args: ${{ matrix.settings.args }}
|
args: ${{ matrix.settings.args }}
|
||||||
# Prevent tauri-action from auto-uploading a latest.json that uses
|
|
||||||
# the PUBLIC KEY as the signature value. Our generate-update-manifest.js
|
|
||||||
# builds the correct manifest with real .sig file contents.
|
|
||||||
updaterJsonKeepUniversal: false
|
|
||||||
|
|
||||||
- name: sign and upload Windows NSIS updater bundle
|
|
||||||
if: matrix.settings.platform == 'windows-latest'
|
|
||||||
shell: pwsh
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
VERSION: ${{ needs.create-release.outputs.package_version }}
|
|
||||||
run: |
|
|
||||||
$dir = "src-tauri/target/release/bundle/nsis"
|
|
||||||
$exe = (Get-ChildItem $dir -Filter "*.exe")[0].FullName
|
|
||||||
$zip = $exe -replace '\.exe$', '.nsis.zip'
|
|
||||||
Compress-Archive -Path $exe -DestinationPath $zip -Force
|
|
||||||
npx tauri signer sign --private-key "$env:TAURI_SIGNING_PRIVATE_KEY" --password "$env:TAURI_SIGNING_PRIVATE_KEY_PASSWORD" $zip
|
|
||||||
gh release upload "app-v$env:VERSION" $zip "$zip.sig" --clobber
|
|
||||||
|
|
||||||
- name: sign and upload macOS bundle signature
|
|
||||||
if: matrix.settings.platform == 'macos-latest'
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
VERSION: ${{ needs.create-release.outputs.package_version }}
|
|
||||||
run: |
|
|
||||||
if [[ "${{ matrix.settings.args }}" == *"aarch64"* ]]; then
|
|
||||||
BUNDLE="src-tauri/target/aarch64-apple-darwin/release/bundle/macos/Psysonic.app.tar.gz"
|
|
||||||
SIG_NAME="Psysonic_aarch64.app.tar.gz.sig"
|
|
||||||
else
|
|
||||||
BUNDLE="src-tauri/target/x86_64-apple-darwin/release/bundle/macos/Psysonic.app.tar.gz"
|
|
||||||
SIG_NAME="Psysonic_x64.app.tar.gz.sig"
|
|
||||||
fi
|
|
||||||
npx tauri signer sign --private-key "$TAURI_SIGNING_PRIVATE_KEY" --password "$TAURI_SIGNING_PRIVATE_KEY_PASSWORD" "$BUNDLE"
|
|
||||||
cp "${BUNDLE}.sig" "$SIG_NAME"
|
|
||||||
gh release upload "app-v${VERSION}" "$SIG_NAME" --clobber
|
|
||||||
|
|
||||||
build-linux:
|
build-linux:
|
||||||
needs: create-release
|
needs: create-release
|
||||||
@@ -213,32 +174,3 @@ jobs:
|
|||||||
find src-tauri/target/release/bundle \
|
find src-tauri/target/release/bundle \
|
||||||
\( -name "*.deb" -o -name "*.rpm" \) \
|
\( -name "*.deb" -o -name "*.rpm" \) \
|
||||||
| xargs gh release upload "app-v${VERSION}" --clobber
|
| xargs gh release upload "app-v${VERSION}" --clobber
|
||||||
|
|
||||||
generate-manifest:
|
|
||||||
needs: [create-release, build-macos-windows]
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
- name: setup node
|
|
||||||
uses: actions/setup-node@v5
|
|
||||||
with:
|
|
||||||
node-version: lts/*
|
|
||||||
- name: cache npm
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: ~/.npm
|
|
||||||
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
|
|
||||||
restore-keys: ${{ runner.os }}-npm-
|
|
||||||
- name: generate latest.json
|
|
||||||
env:
|
|
||||||
VERSION: ${{ needs.create-release.outputs.package_version }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: node scripts/generate-update-manifest.js
|
|
||||||
- name: upload latest.json
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
VERSION=${{ needs.create-release.outputs.package_version }}
|
|
||||||
gh release upload "app-v${VERSION}" latest.json --clobber
|
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
// Generates latest.json for the Tauri updater from a GitHub release.
|
|
||||||
// Reads .sig files uploaded by tauri-action, assembles the manifest, writes latest.json.
|
|
||||||
//
|
|
||||||
// Required env vars: VERSION, GITHUB_TOKEN
|
|
||||||
// Usage: node scripts/generate-update-manifest.js
|
|
||||||
|
|
||||||
const { execSync } = require('child_process');
|
|
||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
const VERSION = process.env.VERSION;
|
|
||||||
const REPO = 'Psychotoxical/psysonic';
|
|
||||||
const TAG = `app-v${VERSION}`;
|
|
||||||
|
|
||||||
if (!VERSION) {
|
|
||||||
console.error('VERSION env var required');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Platform → update bundle filename (produced by tauri-action with updater plugin)
|
|
||||||
const PLATFORM_FILES = {
|
|
||||||
'darwin-aarch64': `Psysonic_aarch64.app.tar.gz`,
|
|
||||||
'darwin-x86_64': `Psysonic_x64.app.tar.gz`,
|
|
||||||
'windows-x86_64': `Psysonic_${VERSION}_x64-setup.nsis.zip`,
|
|
||||||
};
|
|
||||||
|
|
||||||
const platforms = {};
|
|
||||||
|
|
||||||
// A real minisign .sig file is multi-line and ~200+ chars.
|
|
||||||
// A public key (RWTxxx... single line, ~56 chars) must never appear here.
|
|
||||||
function validateSignature(sig, platform, sigFile) {
|
|
||||||
// Public keys start with RWT and are a single short base64 token
|
|
||||||
if (/^RWT[A-Za-z0-9+/]{10,}={0,2}$/.test(sig)) {
|
|
||||||
throw new Error(
|
|
||||||
`${platform}: .sig file "${sigFile}" contains a PUBLIC KEY instead of a signature.\n` +
|
|
||||||
` Got: ${sig}\n` +
|
|
||||||
` The TAURI_SIGNING_PUBLIC_KEY env var must never be used as the signature value.\n` +
|
|
||||||
` Ensure the signing step correctly writes the .sig file from the private key.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (sig.length < 80) {
|
|
||||||
throw new Error(
|
|
||||||
`${platform}: .sig file "${sigFile}" looks too short (${sig.length} chars) to be a valid signature.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [platform, filename] of Object.entries(PLATFORM_FILES)) {
|
|
||||||
const sigFile = `${filename}.sig`;
|
|
||||||
try {
|
|
||||||
execSync(
|
|
||||||
`gh release download "${TAG}" --repo "${REPO}" -p "${sigFile}" --clobber`,
|
|
||||||
{ stdio: 'pipe' }
|
|
||||||
);
|
|
||||||
const signature = fs.readFileSync(sigFile, 'utf8').trim();
|
|
||||||
validateSignature(signature, platform, sigFile);
|
|
||||||
const url = `https://github.com/${REPO}/releases/download/${TAG}/${filename}`;
|
|
||||||
platforms[platform] = { signature, url };
|
|
||||||
console.log(`✓ ${platform}`);
|
|
||||||
} catch (e) {
|
|
||||||
console.warn(`⚠ Skipping ${platform}: ${e.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Object.keys(platforms).length === 0) {
|
|
||||||
console.error('No platforms found — aborting manifest generation');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pull release notes from GitHub
|
|
||||||
let notes = '';
|
|
||||||
try {
|
|
||||||
const raw = execSync(
|
|
||||||
`gh release view "${TAG}" --repo "${REPO}" --json body`,
|
|
||||||
{ stdio: 'pipe' }
|
|
||||||
).toString();
|
|
||||||
notes = JSON.parse(raw).body ?? '';
|
|
||||||
} catch {
|
|
||||||
console.warn('Could not fetch release notes');
|
|
||||||
}
|
|
||||||
|
|
||||||
const manifest = {
|
|
||||||
version: VERSION,
|
|
||||||
notes,
|
|
||||||
pub_date: new Date().toISOString(),
|
|
||||||
platforms,
|
|
||||||
};
|
|
||||||
|
|
||||||
fs.writeFileSync('latest.json', JSON.stringify(manifest, null, 2));
|
|
||||||
console.log(`\nWrote latest.json for v${VERSION} with platforms: ${Object.keys(platforms).join(', ')}`);
|
|
||||||
Generated
+2
-242
@@ -69,15 +69,6 @@ version = "1.0.102"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "arbitrary"
|
|
||||||
version = "1.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
|
|
||||||
dependencies = [
|
|
||||||
"derive_arbitrary",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
@@ -937,17 +928,6 @@ dependencies = [
|
|||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "derive_arbitrary"
|
|
||||||
version = "1.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.117",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_more"
|
name = "derive_more"
|
||||||
version = "0.99.20"
|
version = "0.99.20"
|
||||||
@@ -1260,17 +1240,6 @@ dependencies = [
|
|||||||
"rustc_version",
|
"rustc_version",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "filetime"
|
|
||||||
version = "0.2.27"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"libc",
|
|
||||||
"libredox",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.9"
|
version = "0.1.9"
|
||||||
@@ -2415,10 +2384,7 @@ version = "0.1.14"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a"
|
checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
|
||||||
"libc",
|
"libc",
|
||||||
"plain",
|
|
||||||
"redox_syscall 0.7.3",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2573,12 +2539,6 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "minisign-verify"
|
|
||||||
version = "0.2.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "22f9645cb765ea72b8111f36c522475d2daa0d22c957a9826437e97534bc4e9e"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.8.9"
|
version = "0.8.9"
|
||||||
@@ -2845,7 +2805,6 @@ checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"block2",
|
"block2",
|
||||||
"libc",
|
|
||||||
"objc2",
|
"objc2",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
]
|
]
|
||||||
@@ -2861,18 +2820,6 @@ dependencies = [
|
|||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "objc2-osa-kit"
|
|
||||||
version = "0.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.11.0",
|
|
||||||
"objc2",
|
|
||||||
"objc2-app-kit",
|
|
||||||
"objc2-foundation",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-quartz-core"
|
name = "objc2-quartz-core"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@@ -2952,12 +2899,6 @@ dependencies = [
|
|||||||
"pathdiff",
|
"pathdiff",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl-probe"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "option-ext"
|
name = "option-ext"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -2984,20 +2925,6 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "osakit"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b"
|
|
||||||
dependencies = [
|
|
||||||
"objc2",
|
|
||||||
"objc2-foundation",
|
|
||||||
"objc2-osa-kit",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"thiserror 2.0.18",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pango"
|
name = "pango"
|
||||||
version = "0.18.3"
|
version = "0.18.3"
|
||||||
@@ -3047,7 +2974,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"redox_syscall 0.5.18",
|
"redox_syscall",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"windows-link 0.2.1",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
@@ -3227,12 +3154,6 @@ version = "0.3.32"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "plain"
|
|
||||||
version = "0.2.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "plist"
|
name = "plist"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
@@ -3440,7 +3361,6 @@ dependencies = [
|
|||||||
"tauri-plugin-shell",
|
"tauri-plugin-shell",
|
||||||
"tauri-plugin-single-instance",
|
"tauri-plugin-single-instance",
|
||||||
"tauri-plugin-store",
|
"tauri-plugin-store",
|
||||||
"tauri-plugin-updater",
|
|
||||||
"tauri-plugin-window-state",
|
"tauri-plugin-window-state",
|
||||||
"thread-priority",
|
"thread-priority",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -3663,15 +3583,6 @@ dependencies = [
|
|||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "redox_syscall"
|
|
||||||
version = "0.7.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.11.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_users"
|
name = "redox_users"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
@@ -3789,20 +3700,15 @@ dependencies = [
|
|||||||
"http-body",
|
"http-body",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
"hyper",
|
"hyper",
|
||||||
"hyper-rustls",
|
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"rustls",
|
|
||||||
"rustls-pki-types",
|
|
||||||
"rustls-platform-verifier",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sync_wrapper",
|
"sync_wrapper",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls",
|
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tower",
|
"tower",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
@@ -3941,18 +3847,6 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustls-native-certs"
|
|
||||||
version = "0.8.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63"
|
|
||||||
dependencies = [
|
|
||||||
"openssl-probe",
|
|
||||||
"rustls-pki-types",
|
|
||||||
"schannel",
|
|
||||||
"security-framework",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-pki-types"
|
name = "rustls-pki-types"
|
||||||
version = "1.14.0"
|
version = "1.14.0"
|
||||||
@@ -3963,33 +3857,6 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustls-platform-verifier"
|
|
||||||
version = "0.6.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
|
|
||||||
dependencies = [
|
|
||||||
"core-foundation 0.10.1",
|
|
||||||
"core-foundation-sys",
|
|
||||||
"jni",
|
|
||||||
"log",
|
|
||||||
"once_cell",
|
|
||||||
"rustls",
|
|
||||||
"rustls-native-certs",
|
|
||||||
"rustls-platform-verifier-android",
|
|
||||||
"rustls-webpki",
|
|
||||||
"security-framework",
|
|
||||||
"security-framework-sys",
|
|
||||||
"webpki-root-certs",
|
|
||||||
"windows-sys 0.61.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustls-platform-verifier-android"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-webpki"
|
name = "rustls-webpki"
|
||||||
version = "0.103.9"
|
version = "0.103.9"
|
||||||
@@ -4022,15 +3889,6 @@ dependencies = [
|
|||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "schannel"
|
|
||||||
version = "0.1.29"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
|
|
||||||
dependencies = [
|
|
||||||
"windows-sys 0.61.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "schemars"
|
name = "schemars"
|
||||||
version = "0.8.22"
|
version = "0.8.22"
|
||||||
@@ -4088,29 +3946,6 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "security-framework"
|
|
||||||
version = "3.7.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.11.0",
|
|
||||||
"core-foundation 0.10.1",
|
|
||||||
"core-foundation-sys",
|
|
||||||
"libc",
|
|
||||||
"security-framework-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "security-framework-sys"
|
|
||||||
version = "2.17.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
|
|
||||||
dependencies = [
|
|
||||||
"core-foundation-sys",
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "selectors"
|
name = "selectors"
|
||||||
version = "0.24.0"
|
version = "0.24.0"
|
||||||
@@ -4444,7 +4279,7 @@ dependencies = [
|
|||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
"objc2-quartz-core",
|
"objc2-quartz-core",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"redox_syscall 0.5.18",
|
"redox_syscall",
|
||||||
"tracing",
|
"tracing",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
@@ -4818,17 +4653,6 @@ dependencies = [
|
|||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tar"
|
|
||||||
version = "0.4.45"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973"
|
|
||||||
dependencies = [
|
|
||||||
"filetime",
|
|
||||||
"libc",
|
|
||||||
"xattr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "target-lexicon"
|
name = "target-lexicon"
|
||||||
version = "0.12.16"
|
version = "0.12.16"
|
||||||
@@ -5084,39 +4908,6 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tauri-plugin-updater"
|
|
||||||
version = "2.10.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3fe8e9bebd88fc222938ffdfbdcfa0307081423bd01e3252fc337d8bde81fc61"
|
|
||||||
dependencies = [
|
|
||||||
"base64 0.22.1",
|
|
||||||
"dirs",
|
|
||||||
"flate2",
|
|
||||||
"futures-util",
|
|
||||||
"http",
|
|
||||||
"infer",
|
|
||||||
"log",
|
|
||||||
"minisign-verify",
|
|
||||||
"osakit",
|
|
||||||
"percent-encoding",
|
|
||||||
"reqwest 0.13.2",
|
|
||||||
"rustls",
|
|
||||||
"semver",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"tar",
|
|
||||||
"tauri",
|
|
||||||
"tauri-plugin",
|
|
||||||
"tempfile",
|
|
||||||
"thiserror 2.0.18",
|
|
||||||
"time",
|
|
||||||
"tokio",
|
|
||||||
"url",
|
|
||||||
"windows-sys 0.60.2",
|
|
||||||
"zip",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-window-state"
|
name = "tauri-plugin-window-state"
|
||||||
version = "2.4.1"
|
version = "2.4.1"
|
||||||
@@ -6046,15 +5837,6 @@ dependencies = [
|
|||||||
"system-deps",
|
"system-deps",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "webpki-root-certs"
|
|
||||||
version = "1.0.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca"
|
|
||||||
dependencies = [
|
|
||||||
"rustls-pki-types",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webpki-roots"
|
name = "webpki-roots"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
@@ -6843,16 +6625,6 @@ version = "0.13.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "xattr"
|
|
||||||
version = "1.6.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"rustix 1.1.4",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xdg-home"
|
name = "xdg-home"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
@@ -7099,18 +6871,6 @@ dependencies = [
|
|||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zip"
|
|
||||||
version = "4.6.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1"
|
|
||||||
dependencies = [
|
|
||||||
"arbitrary",
|
|
||||||
"crc32fast",
|
|
||||||
"indexmap 2.13.0",
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zmij"
|
name = "zmij"
|
||||||
version = "1.0.21"
|
version = "1.0.21"
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ tokio = { version = "1", features = ["rt", "time"] }
|
|||||||
biquad = "0.4"
|
biquad = "0.4"
|
||||||
ringbuf = "0.3"
|
ringbuf = "0.3"
|
||||||
tauri-plugin-window-state = "2.4.1"
|
tauri-plugin-window-state = "2.4.1"
|
||||||
tauri-plugin-updater = "2"
|
|
||||||
tauri-plugin-process = "2"
|
tauri-plugin-process = "2"
|
||||||
souvlaki = { version = "0.8", default-features = false, features = ["use_zbus"] }
|
souvlaki = { version = "0.8", default-features = false, features = ["use_zbus"] }
|
||||||
discord-rich-presence = "0.2"
|
discord-rich-presence = "0.2"
|
||||||
|
|||||||
@@ -34,7 +34,6 @@
|
|||||||
"core:window:allow-is-fullscreen",
|
"core:window:allow-is-fullscreen",
|
||||||
"core:window:allow-create",
|
"core:window:allow-create",
|
||||||
"core:webview:allow-create-webview-window",
|
"core:webview:allow-create-webview-window",
|
||||||
"updater:default",
|
|
||||||
"process:allow-restart"
|
"process:allow-restart"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
|||||||
{"default":{"identifier":"default","description":"Default capabilities for Psysonic","local":true,"windows":["main"],"permissions":["core:default","shell:default",{"identifier":"shell:allow-open","allow":[{"url":"https://**"}]},"global-shortcut:allow-register","global-shortcut:allow-unregister","store:default","store:allow-load","store:allow-set","store:allow-get","store:allow-save","dialog:default","dialog:allow-open","dialog:allow-save","fs:default","fs:allow-write-file","fs:allow-read-file","fs:allow-mkdir","fs:scope-download-recursive","fs:scope-home-recursive","window-state:allow-save-window-state","window-state:allow-restore-state","core:window:allow-set-title","core:window:allow-close","core:window:allow-hide","core:window:allow-show","core:window:allow-set-fullscreen","core:window:allow-is-fullscreen","core:window:allow-create","core:webview:allow-create-webview-window","updater:default","process:allow-restart"],"platforms":["linux","macOS","windows"]}}
|
{"default":{"identifier":"default","description":"Default capabilities for Psysonic","local":true,"windows":["main"],"permissions":["core:default","shell:default",{"identifier":"shell:allow-open","allow":[{"url":"https://**"}]},"global-shortcut:allow-register","global-shortcut:allow-unregister","store:default","store:allow-load","store:allow-set","store:allow-get","store:allow-save","dialog:default","dialog:allow-open","dialog:allow-save","fs:default","fs:allow-write-file","fs:allow-read-file","fs:allow-mkdir","fs:scope-download-recursive","fs:scope-home-recursive","window-state:allow-save-window-state","window-state:allow-restore-state","core:window:allow-set-title","core:window:allow-close","core:window:allow-hide","core:window:allow-show","core:window:allow-set-fullscreen","core:window:allow-is-fullscreen","core:window:allow-create","core:webview:allow-create-webview-window","process:allow-restart"],"platforms":["linux","macOS","windows"]}}
|
||||||
@@ -6284,60 +6284,6 @@
|
|||||||
"const": "store:deny-values",
|
"const": "store:deny-values",
|
||||||
"markdownDescription": "Denies the values command without any pre-configured scope."
|
"markdownDescription": "Denies the values command without any pre-configured scope."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"description": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`",
|
|
||||||
"type": "string",
|
|
||||||
"const": "updater:default",
|
|
||||||
"markdownDescription": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Enables the check command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "updater:allow-check",
|
|
||||||
"markdownDescription": "Enables the check command without any pre-configured scope."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Enables the download command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "updater:allow-download",
|
|
||||||
"markdownDescription": "Enables the download command without any pre-configured scope."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Enables the download_and_install command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "updater:allow-download-and-install",
|
|
||||||
"markdownDescription": "Enables the download_and_install command without any pre-configured scope."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Enables the install command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "updater:allow-install",
|
|
||||||
"markdownDescription": "Enables the install command without any pre-configured scope."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Denies the check command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "updater:deny-check",
|
|
||||||
"markdownDescription": "Denies the check command without any pre-configured scope."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Denies the download command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "updater:deny-download",
|
|
||||||
"markdownDescription": "Denies the download command without any pre-configured scope."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Denies the download_and_install command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "updater:deny-download-and-install",
|
|
||||||
"markdownDescription": "Denies the download_and_install command without any pre-configured scope."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Denies the install command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "updater:deny-install",
|
|
||||||
"markdownDescription": "Denies the install command without any pre-configured scope."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"description": "This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n\n#### This default permission set includes:\n\n- `allow-filename`\n- `allow-restore-state`\n- `allow-save-window-state`",
|
"description": "This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n\n#### This default permission set includes:\n\n- `allow-filename`\n- `allow-restore-state`\n- `allow-save-window-state`",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
@@ -6284,60 +6284,6 @@
|
|||||||
"const": "store:deny-values",
|
"const": "store:deny-values",
|
||||||
"markdownDescription": "Denies the values command without any pre-configured scope."
|
"markdownDescription": "Denies the values command without any pre-configured scope."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"description": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`",
|
|
||||||
"type": "string",
|
|
||||||
"const": "updater:default",
|
|
||||||
"markdownDescription": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Enables the check command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "updater:allow-check",
|
|
||||||
"markdownDescription": "Enables the check command without any pre-configured scope."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Enables the download command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "updater:allow-download",
|
|
||||||
"markdownDescription": "Enables the download command without any pre-configured scope."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Enables the download_and_install command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "updater:allow-download-and-install",
|
|
||||||
"markdownDescription": "Enables the download_and_install command without any pre-configured scope."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Enables the install command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "updater:allow-install",
|
|
||||||
"markdownDescription": "Enables the install command without any pre-configured scope."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Denies the check command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "updater:deny-check",
|
|
||||||
"markdownDescription": "Denies the check command without any pre-configured scope."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Denies the download command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "updater:deny-download",
|
|
||||||
"markdownDescription": "Denies the download command without any pre-configured scope."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Denies the download_and_install command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "updater:deny-download-and-install",
|
|
||||||
"markdownDescription": "Denies the download_and_install command without any pre-configured scope."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Denies the install command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "updater:deny-install",
|
|
||||||
"markdownDescription": "Denies the install command without any pre-configured scope."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"description": "This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n\n#### This default permission set includes:\n\n- `allow-filename`\n- `allow-restore-state`\n- `allow-save-window-state`",
|
"description": "This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n\n#### This default permission set includes:\n\n- `allow-filename`\n- `allow-restore-state`\n- `allow-save-window-state`",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
@@ -36,49 +36,6 @@ fn exit_app(app_handle: tauri::AppHandle) {
|
|||||||
app_handle.exit(0);
|
app_handle.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Restart after an in-app update.
|
|
||||||
///
|
|
||||||
/// `relaunch()` from tauri-plugin-process spawns the new process while the old one
|
|
||||||
/// is still alive, so the single-instance plugin in the new process sees the old
|
|
||||||
/// socket and kills itself immediately (endless loop).
|
|
||||||
///
|
|
||||||
/// This command instead:
|
|
||||||
/// 1. Spawns a shell snippet that waits ~1.5 s and then opens the app again —
|
|
||||||
/// by then the old process and its single-instance socket are fully gone.
|
|
||||||
/// 2. Calls `app.exit(0)` directly, which bypasses `on_window_event`
|
|
||||||
/// prevent_close() and releases the single-instance lock immediately.
|
|
||||||
#[tauri::command]
|
|
||||||
fn relaunch_after_update(app: tauri::AppHandle) {
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
{
|
|
||||||
let exe = std::env::current_exe().unwrap_or_default();
|
|
||||||
let exe_str = exe.to_string_lossy().to_string().replace('\'', "''");
|
|
||||||
let script = format!(
|
|
||||||
"Start-Sleep -Milliseconds 1500; Start-Process '{exe_str}'"
|
|
||||||
);
|
|
||||||
let _ = std::process::Command::new("powershell")
|
|
||||||
.args(["-WindowStyle", "Hidden", "-NonInteractive", "-Command", &script])
|
|
||||||
.spawn();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
{
|
|
||||||
let exe = std::env::current_exe().unwrap_or_default();
|
|
||||||
// exe lives at Psysonic.app/Contents/MacOS/psysonic — walk up to the bundle.
|
|
||||||
let bundle = exe
|
|
||||||
.parent() // MacOS/
|
|
||||||
.and_then(|p| p.parent()) // Contents/
|
|
||||||
.and_then(|p| p.parent()); // Psysonic.app
|
|
||||||
if let Some(bundle_path) = bundle {
|
|
||||||
let escaped = bundle_path.to_string_lossy().replace('"', "\\\"");
|
|
||||||
let _ = std::process::Command::new("sh")
|
|
||||||
.args(["-c", &format!("sleep 1.5 && open \"{escaped}\"")])
|
|
||||||
.spawn();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
app.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Authenticate with Navidrome's own REST API and return a Bearer token.
|
/// Authenticate with Navidrome's own REST API and return a Bearer token.
|
||||||
async fn navidrome_token(server_url: &str, username: &str, password: &str) -> Result<String, String> {
|
async fn navidrome_token(server_url: &str, username: &str, password: &str) -> Result<String, String> {
|
||||||
@@ -854,7 +811,6 @@ pub fn run() {
|
|||||||
.manage(ShortcutMap::default())
|
.manage(ShortcutMap::default())
|
||||||
.manage(discord::DiscordState::new())
|
.manage(discord::DiscordState::new())
|
||||||
.manage(TrayState::default())
|
.manage(TrayState::default())
|
||||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
|
||||||
.plugin(tauri_plugin_process::init())
|
.plugin(tauri_plugin_process::init())
|
||||||
.plugin(tauri_plugin_window_state::Builder::default().build())
|
.plugin(tauri_plugin_window_state::Builder::default().build())
|
||||||
.plugin(tauri_plugin_shell::init())
|
.plugin(tauri_plugin_shell::init())
|
||||||
@@ -1031,7 +987,6 @@ pub fn run() {
|
|||||||
get_hot_cache_size,
|
get_hot_cache_size,
|
||||||
delete_hot_cache_track,
|
delete_hot_cache_track,
|
||||||
purge_hot_cache,
|
purge_hot_cache,
|
||||||
relaunch_after_update,
|
|
||||||
toggle_tray_icon,
|
toggle_tray_icon,
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
|
|||||||
@@ -31,14 +31,6 @@
|
|||||||
"csp": null
|
"csp": null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"plugins": {
|
|
||||||
"updater": {
|
|
||||||
"pubkey": "RWTgsDNd9LqppH6DMq7ypolI0bsLCNsjOvif4mrHyr4UDidkUT69IY1K",
|
|
||||||
"endpoints": [
|
|
||||||
"https://github.com/Psychotoxical/psysonic/releases/latest/download/latest.json"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"active": true,
|
"active": true,
|
||||||
"targets": "all",
|
"targets": "all",
|
||||||
|
|||||||
+21
-100
@@ -1,9 +1,7 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { check, Update, DownloadEvent } from '@tauri-apps/plugin-updater';
|
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
|
||||||
import { open } from '@tauri-apps/plugin-shell';
|
import { open } from '@tauri-apps/plugin-shell';
|
||||||
import { RefreshCw, Download, X } from 'lucide-react';
|
import { RefreshCw, X } from 'lucide-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { version as currentVersion } from '../../package.json';
|
import { version as currentVersion } from '../../package.json';
|
||||||
|
|
||||||
@@ -18,85 +16,31 @@ function isNewer(a: string, b: string): boolean {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
type State =
|
|
||||||
| { phase: 'idle' }
|
|
||||||
| { phase: 'available'; version: string; update: Update | null; error?: string }
|
|
||||||
| { phase: 'downloading'; pct: number }
|
|
||||||
| { phase: 'installing' }
|
|
||||||
| { phase: 'done' };
|
|
||||||
|
|
||||||
export default function AppUpdater() {
|
export default function AppUpdater() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [state, setState] = useState<State>({ phase: 'idle' });
|
const [newVersion, setNewVersion] = useState<string | null>(null);
|
||||||
const [dismissed, setDismissed] = useState(false);
|
const [dismissed, setDismissed] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
const timer = setTimeout(async () => {
|
const timer = setTimeout(async () => {
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
try {
|
|
||||||
// Try Tauri native updater first (macOS + Windows)
|
|
||||||
const update = await check();
|
|
||||||
if (cancelled) return;
|
|
||||||
if (update) {
|
|
||||||
setState({ phase: 'available', version: update.version, update });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// Tauri updater unavailable or network error — fall through to GitHub check
|
|
||||||
}
|
|
||||||
// Fallback: GitHub API check (Linux / offline Tauri updater)
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch('https://api.github.com/repos/Psychotoxical/psysonic/releases/latest');
|
const res = await fetch('https://api.github.com/repos/Psychotoxical/psysonic/releases/latest');
|
||||||
if (!res.ok || cancelled) return;
|
if (!res.ok || cancelled) return;
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
const tag: string = data.tag_name ?? '';
|
const tag: string = data.tag_name ?? '';
|
||||||
if (!cancelled && tag && isNewer(tag, currentVersion)) {
|
if (!cancelled && tag && isNewer(tag, currentVersion)) {
|
||||||
setState({ phase: 'available', version: tag.replace(/^[^0-9]*/, ''), update: null });
|
setNewVersion(tag.replace(/^[^0-9]*/, ''));
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// No update check possible — stay idle
|
// No network or rate-limited — stay idle
|
||||||
}
|
}
|
||||||
}, 3000);
|
}, 4000);
|
||||||
return () => { cancelled = true; clearTimeout(timer); };
|
return () => { cancelled = true; clearTimeout(timer); };
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (dismissed || state.phase === 'idle' || state.phase === 'done') return null;
|
if (!newVersion || dismissed) return null;
|
||||||
|
|
||||||
const handleInstall = async () => {
|
|
||||||
if (state.phase !== 'available' || !state.update) return;
|
|
||||||
const update = state.update;
|
|
||||||
const savedVersion = state.version;
|
|
||||||
let total = 0;
|
|
||||||
let downloaded = 0;
|
|
||||||
setState({ phase: 'downloading', pct: 0 });
|
|
||||||
try {
|
|
||||||
await update.downloadAndInstall((event: DownloadEvent) => {
|
|
||||||
if (event.event === 'Started') {
|
|
||||||
total = event.data.contentLength ?? 0;
|
|
||||||
} else if (event.event === 'Progress') {
|
|
||||||
downloaded += event.data.chunkLength;
|
|
||||||
setState({ phase: 'downloading', pct: total ? Math.round((downloaded / total) * 100) : 0 });
|
|
||||||
} else if (event.event === 'Finished') {
|
|
||||||
setState({ phase: 'installing' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await invoke('relaunch_after_update');
|
|
||||||
} catch (e: unknown) {
|
|
||||||
const msg = e instanceof Error ? e.message : String(e);
|
|
||||||
console.error('Update failed:', msg);
|
|
||||||
// Surface the error so the user (and developer) can see what went wrong
|
|
||||||
setState({ phase: 'available', version: savedVersion, update, error: msg });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDownload = () => {
|
|
||||||
open('https://github.com/Psychotoxical/psysonic/releases/latest');
|
|
||||||
};
|
|
||||||
|
|
||||||
const version = state.phase === 'available' ? state.version : '';
|
|
||||||
const canInstall = state.phase === 'available' && state.update !== null;
|
|
||||||
const isLinuxFallback = state.phase === 'available' && state.update === null;
|
|
||||||
|
|
||||||
return createPortal(
|
return createPortal(
|
||||||
<div className="app-updater-toast">
|
<div className="app-updater-toast">
|
||||||
@@ -107,44 +51,21 @@ export default function AppUpdater() {
|
|||||||
<X size={12} />
|
<X size={12} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="app-updater-version">{t('common.updaterVersion', { version })}</div>
|
<div className="app-updater-version">{t('common.updaterVersion', { version: newVersion })}</div>
|
||||||
|
<div className="app-updater-actions">
|
||||||
{state.phase === 'downloading' && (
|
<button
|
||||||
<div className="app-updater-progress-wrap">
|
className="app-updater-btn-primary"
|
||||||
<div className="app-updater-progress-bar">
|
onClick={() => open('https://github.com/Psychotoxical/psysonic/releases/latest')}
|
||||||
<div className="app-updater-progress-fill" style={{ width: `${state.pct}%` }} />
|
>
|
||||||
</div>
|
GitHub
|
||||||
<span className="app-updater-pct">{state.pct}%</span>
|
</button>
|
||||||
</div>
|
<button
|
||||||
)}
|
className="app-updater-btn-secondary"
|
||||||
|
onClick={() => open('https://psysonic.psychotoxic.eu/#downloads')}
|
||||||
{state.phase === 'installing' && (
|
>
|
||||||
<div className="app-updater-status">{t('common.updaterInstalling')}</div>
|
{t('common.updaterWebsite')}
|
||||||
)}
|
</button>
|
||||||
|
</div>
|
||||||
{state.phase === 'available' && (
|
|
||||||
<div className="app-updater-actions">
|
|
||||||
{state.error && (
|
|
||||||
<div className="app-updater-error">{state.error}</div>
|
|
||||||
)}
|
|
||||||
{canInstall && (
|
|
||||||
<>
|
|
||||||
<p className="app-updater-hint">{t('common.updaterExperimentalHint')}</p>
|
|
||||||
<button className="app-updater-btn-primary" onClick={handleInstall}>
|
|
||||||
<Download size={12} /> {t('common.updaterInstall')}
|
|
||||||
</button>
|
|
||||||
<button className="app-updater-btn-secondary" onClick={handleDownload}>
|
|
||||||
<Download size={12} /> {t('common.updaterDownload')}
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{isLinuxFallback && (
|
|
||||||
<button className="app-updater-btn-primary" onClick={handleDownload}>
|
|
||||||
<Download size={12} /> {t('common.updaterDownload')}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>,
|
</div>,
|
||||||
document.body
|
document.body
|
||||||
);
|
);
|
||||||
|
|||||||
+2
-9
@@ -15,9 +15,6 @@ export const deTranslation = {
|
|||||||
help: 'Hilfe',
|
help: 'Hilfe',
|
||||||
expand: 'Sidebar einblenden',
|
expand: 'Sidebar einblenden',
|
||||||
collapse: 'Sidebar ausblenden',
|
collapse: 'Sidebar ausblenden',
|
||||||
updateAvailable: 'Update verfügbar',
|
|
||||||
updateReady: '{{version}} ist bereit',
|
|
||||||
updateLink: 'Zum Release →',
|
|
||||||
downloadingTracks: '{{n}} Tracks werden gecacht…',
|
downloadingTracks: '{{n}} Tracks werden gecacht…',
|
||||||
offlineLibrary: 'Offline-Bibliothek',
|
offlineLibrary: 'Offline-Bibliothek',
|
||||||
genres: 'Genres',
|
genres: 'Genres',
|
||||||
@@ -328,12 +325,8 @@ export const deTranslation = {
|
|||||||
bulkRemoveFromPlaylist: 'Aus Playlist entfernen',
|
bulkRemoveFromPlaylist: 'Aus Playlist entfernen',
|
||||||
bulkClear: 'Auswahl aufheben',
|
bulkClear: 'Auswahl aufheben',
|
||||||
updaterAvailable: 'Update verfügbar',
|
updaterAvailable: 'Update verfügbar',
|
||||||
updaterVersion: 'v{{version}} ist bereit',
|
updaterVersion: 'v{{version}} verfügbar',
|
||||||
updaterInstall: 'Installieren & Neustart',
|
updaterWebsite: 'Website',
|
||||||
updaterDownloading: 'Wird geladen…',
|
|
||||||
updaterInstalling: 'Wird installiert…',
|
|
||||||
updaterDownload: 'Von GitHub herunterladen',
|
|
||||||
updaterExperimentalHint: 'Automatische Updates sind noch in Entwicklung',
|
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
title: 'Einstellungen',
|
title: 'Einstellungen',
|
||||||
|
|||||||
+3
-9
@@ -15,9 +15,7 @@ export const enTranslation = {
|
|||||||
help: 'Help',
|
help: 'Help',
|
||||||
expand: 'Expand Sidebar',
|
expand: 'Expand Sidebar',
|
||||||
collapse: 'Collapse Sidebar',
|
collapse: 'Collapse Sidebar',
|
||||||
updateAvailable: 'Update available',
|
|
||||||
updateReady: '{{version}} is ready',
|
|
||||||
updateLink: 'Go to release →',
|
|
||||||
downloadingTracks: 'Caching {{n}} tracks…',
|
downloadingTracks: 'Caching {{n}} tracks…',
|
||||||
offlineLibrary: 'Offline Library',
|
offlineLibrary: 'Offline Library',
|
||||||
genres: 'Genres',
|
genres: 'Genres',
|
||||||
@@ -328,12 +326,8 @@ export const enTranslation = {
|
|||||||
bulkRemoveFromPlaylist: 'Remove from Playlist',
|
bulkRemoveFromPlaylist: 'Remove from Playlist',
|
||||||
bulkClear: 'Clear selection',
|
bulkClear: 'Clear selection',
|
||||||
updaterAvailable: 'Update available',
|
updaterAvailable: 'Update available',
|
||||||
updaterVersion: 'v{{version}} is ready',
|
updaterVersion: 'v{{version}} is available',
|
||||||
updaterInstall: 'Install & Restart',
|
updaterWebsite: 'Website',
|
||||||
updaterDownloading: 'Downloading…',
|
|
||||||
updaterInstalling: 'Installing…',
|
|
||||||
updaterDownload: 'Download from GitHub',
|
|
||||||
updaterExperimentalHint: 'Auto-update is still in development',
|
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
title: 'Settings',
|
title: 'Settings',
|
||||||
|
|||||||
+2
-9
@@ -15,9 +15,6 @@ export const frTranslation = {
|
|||||||
help: 'Aide',
|
help: 'Aide',
|
||||||
expand: 'Développer la barre latérale',
|
expand: 'Développer la barre latérale',
|
||||||
collapse: 'Réduire la barre latérale',
|
collapse: 'Réduire la barre latérale',
|
||||||
updateAvailable: 'Mise à jour disponible',
|
|
||||||
updateReady: '{{version}} est prêt',
|
|
||||||
updateLink: 'Voir la version →',
|
|
||||||
downloadingTracks: '{{n}} pistes en cache…',
|
downloadingTracks: '{{n}} pistes en cache…',
|
||||||
offlineLibrary: 'Bibliothèque hors ligne',
|
offlineLibrary: 'Bibliothèque hors ligne',
|
||||||
genres: 'Genres',
|
genres: 'Genres',
|
||||||
@@ -328,12 +325,8 @@ export const frTranslation = {
|
|||||||
bulkRemoveFromPlaylist: 'Retirer de la playlist',
|
bulkRemoveFromPlaylist: 'Retirer de la playlist',
|
||||||
bulkClear: 'Désélectionner',
|
bulkClear: 'Désélectionner',
|
||||||
updaterAvailable: 'Mise à jour disponible',
|
updaterAvailable: 'Mise à jour disponible',
|
||||||
updaterVersion: 'v{{version}} est prête',
|
updaterVersion: 'v{{version}} est disponible',
|
||||||
updaterInstall: 'Installer & Redémarrer',
|
updaterWebsite: 'Site Web',
|
||||||
updaterDownloading: 'Téléchargement…',
|
|
||||||
updaterInstalling: 'Installation…',
|
|
||||||
updaterDownload: 'Télécharger depuis GitHub',
|
|
||||||
updaterExperimentalHint: 'La mise à jour automatique est encore en développement',
|
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
title: 'Paramètres',
|
title: 'Paramètres',
|
||||||
|
|||||||
+2
-9
@@ -15,9 +15,6 @@ export const nbTranslation = {
|
|||||||
help: 'Hjelp',
|
help: 'Hjelp',
|
||||||
expand: 'Utvid sidefelt',
|
expand: 'Utvid sidefelt',
|
||||||
collapse: 'Skjul sidefelt',
|
collapse: 'Skjul sidefelt',
|
||||||
updateAvailable: 'Ny versjon er tilgjengelig',
|
|
||||||
updateReady: '{{version}} er klar',
|
|
||||||
updateLink: 'Hent nyeste versjon →',
|
|
||||||
downloadingTracks: 'Bufre {{n}} spor…',
|
downloadingTracks: 'Bufre {{n}} spor…',
|
||||||
offlineLibrary: 'Frakoblet bibliotek',
|
offlineLibrary: 'Frakoblet bibliotek',
|
||||||
genres: 'Sjangere',
|
genres: 'Sjangere',
|
||||||
@@ -328,12 +325,8 @@ export const nbTranslation = {
|
|||||||
bulkRemoveFromPlaylist: 'Fjern fra spilleliste',
|
bulkRemoveFromPlaylist: 'Fjern fra spilleliste',
|
||||||
bulkClear: 'Tøm utvalg',
|
bulkClear: 'Tøm utvalg',
|
||||||
updaterAvailable: 'Ny versjon er tilgjengelig',
|
updaterAvailable: 'Ny versjon er tilgjengelig',
|
||||||
updaterVersion: 'v{{version}} er lansert',
|
updaterVersion: 'v{{version}} er tilgjengelig',
|
||||||
updaterInstall: 'Installer og start applikasjonen på nytt',
|
updaterWebsite: 'Nettsted',
|
||||||
updaterDownloading: 'Laster ned…',
|
|
||||||
updaterInstalling: 'Installerer…',
|
|
||||||
updaterDownload: 'Last ned fra GitHub',
|
|
||||||
updaterExperimentalHint: 'Auto-oppdatering er fortsatt under utvikling',
|
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
title: 'Innstillinger',
|
title: 'Innstillinger',
|
||||||
|
|||||||
+2
-9
@@ -15,9 +15,6 @@ export const nlTranslation = {
|
|||||||
help: 'Help',
|
help: 'Help',
|
||||||
expand: 'Zijbalk uitklappen',
|
expand: 'Zijbalk uitklappen',
|
||||||
collapse: 'Zijbalk inklappen',
|
collapse: 'Zijbalk inklappen',
|
||||||
updateAvailable: 'Update beschikbaar',
|
|
||||||
updateReady: '{{version}} is klaar',
|
|
||||||
updateLink: 'Naar release →',
|
|
||||||
downloadingTracks: '{{n}} nummers worden gecached…',
|
downloadingTracks: '{{n}} nummers worden gecached…',
|
||||||
offlineLibrary: 'Offline bibliotheek',
|
offlineLibrary: 'Offline bibliotheek',
|
||||||
genres: 'Genres',
|
genres: 'Genres',
|
||||||
@@ -328,12 +325,8 @@ export const nlTranslation = {
|
|||||||
bulkRemoveFromPlaylist: 'Verwijderen uit afspeellijst',
|
bulkRemoveFromPlaylist: 'Verwijderen uit afspeellijst',
|
||||||
bulkClear: 'Selectie wissen',
|
bulkClear: 'Selectie wissen',
|
||||||
updaterAvailable: 'Update beschikbaar',
|
updaterAvailable: 'Update beschikbaar',
|
||||||
updaterVersion: 'v{{version}} is klaar',
|
updaterVersion: 'v{{version}} beschikbaar',
|
||||||
updaterInstall: 'Installeren & Herstarten',
|
updaterWebsite: 'Website',
|
||||||
updaterDownloading: 'Downloaden…',
|
|
||||||
updaterInstalling: 'Installeren…',
|
|
||||||
updaterDownload: 'Downloaden van GitHub',
|
|
||||||
updaterExperimentalHint: 'Automatisch bijwerken is nog in ontwikkeling',
|
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
title: 'Instellingen',
|
title: 'Instellingen',
|
||||||
|
|||||||
+2
-9
@@ -16,9 +16,6 @@ export const ruTranslation = {
|
|||||||
help: 'Справка',
|
help: 'Справка',
|
||||||
expand: 'Развернуть боковую панель',
|
expand: 'Развернуть боковую панель',
|
||||||
collapse: 'Свернуть боковую панель',
|
collapse: 'Свернуть боковую панель',
|
||||||
updateAvailable: 'Доступно обновление',
|
|
||||||
updateReady: '{{version}} готово',
|
|
||||||
updateLink: 'Перейти к релизу →',
|
|
||||||
downloadingTracks: 'Кэширование {{n}} треков…',
|
downloadingTracks: 'Кэширование {{n}} треков…',
|
||||||
offlineLibrary: 'Офлайн-библиотека',
|
offlineLibrary: 'Офлайн-библиотека',
|
||||||
genres: 'Жанры',
|
genres: 'Жанры',
|
||||||
@@ -342,12 +339,8 @@ export const ruTranslation = {
|
|||||||
bulkRemoveFromPlaylist: 'Убрать из плейлиста',
|
bulkRemoveFromPlaylist: 'Убрать из плейлиста',
|
||||||
bulkClear: 'Снять выделение',
|
bulkClear: 'Снять выделение',
|
||||||
updaterAvailable: 'Доступно обновление',
|
updaterAvailable: 'Доступно обновление',
|
||||||
updaterVersion: 'Версия {{version}} готова',
|
updaterVersion: 'Версия {{version}} доступна',
|
||||||
updaterInstall: 'Установить и перезапустить',
|
updaterWebsite: 'Сайт',
|
||||||
updaterDownloading: 'Скачивание…',
|
|
||||||
updaterInstalling: 'Установка…',
|
|
||||||
updaterDownload: 'Скачать с GitHub',
|
|
||||||
updaterExperimentalHint: 'Автообновление в разработке',
|
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
title: 'Настройки',
|
title: 'Настройки',
|
||||||
|
|||||||
+2
-9
@@ -15,9 +15,6 @@ export const zhTranslation = {
|
|||||||
help: '帮助',
|
help: '帮助',
|
||||||
expand: '展开侧边栏',
|
expand: '展开侧边栏',
|
||||||
collapse: '收起侧边栏',
|
collapse: '收起侧边栏',
|
||||||
updateAvailable: '有可用更新',
|
|
||||||
updateReady: '{{version}} 已就绪',
|
|
||||||
updateLink: '前往发布页面 →',
|
|
||||||
downloadingTracks: '正在缓存 {{n}} 首歌曲…',
|
downloadingTracks: '正在缓存 {{n}} 首歌曲…',
|
||||||
offlineLibrary: '离线音乐库',
|
offlineLibrary: '离线音乐库',
|
||||||
genres: '流派',
|
genres: '流派',
|
||||||
@@ -324,12 +321,8 @@ export const zhTranslation = {
|
|||||||
bulkRemoveFromPlaylist: '从播放列表移除',
|
bulkRemoveFromPlaylist: '从播放列表移除',
|
||||||
bulkClear: '取消选择',
|
bulkClear: '取消选择',
|
||||||
updaterAvailable: '有可用更新',
|
updaterAvailable: '有可用更新',
|
||||||
updaterVersion: 'v{{version}} 已就绪',
|
updaterVersion: 'v{{version}} 已发布',
|
||||||
updaterInstall: '安装并重启',
|
updaterWebsite: '官网',
|
||||||
updaterDownloading: '下载中…',
|
|
||||||
updaterInstalling: '安装中…',
|
|
||||||
updaterDownload: '从 GitHub 下载',
|
|
||||||
updaterExperimentalHint: '自动更新功能仍在开发中',
|
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
title: '设置',
|
title: '设置',
|
||||||
|
|||||||
Reference in New Issue
Block a user