From 7e0cffc892a9c4bd760aea54c00be7fcb5628735 Mon Sep 17 00:00:00 2001 From: Psychotoxical Date: Thu, 12 Mar 2026 20:04:28 +0100 Subject: [PATCH] feat: album download support and GPU compatibility fixes (v1.0.4) --- .github/workflows/release.yml | 104 +++++++++++++++++++++++++++------- CHANGELOG.md | 9 +++ package.json | 2 +- src-tauri/Cargo.lock | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/tauri.conf.json | 2 +- src/pages/AlbumDetail.tsx | 46 ++++++++++++--- src/styles/components.css | 34 ++++++++++- 8 files changed, 170 insertions(+), 31 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 43f48ad5..6f546039 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,7 +6,42 @@ on: workflow_dispatch: jobs: - release: + create-release: + permissions: + contents: write + runs-on: ubuntu-latest + outputs: + release_id: ${{ steps.create-release.outputs.result }} + package_version: ${{ steps.get-version.outputs.version }} + steps: + - uses: actions/checkout@v4 + - name: setup node + uses: actions/setup-node@v4 + with: + node-version: lts/* + - name: get version + id: get-version + run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT + - name: create release + id: create-release + uses: actions/github-script@v7 + env: + PACKAGE_VERSION: ${{ steps.get-version.outputs.version }} + with: + script: | + const { data } = await github.rest.repos.createRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + tag_name: `app-v${process.env.PACKAGE_VERSION}`, + name: `Psysonic v${process.env.PACKAGE_VERSION}`, + body: 'See the assets to download this version and install.', + draft: false, + prerelease: false + }) + return data.id + + build-macos-windows: + needs: create-release permissions: contents: write strategy: @@ -17,17 +52,37 @@ jobs: args: '--target aarch64-apple-darwin' - platform: 'macos-latest' args: '--target x86_64-apple-darwin' - - platform: 'ubuntu-22.04' - args: '' - platform: 'windows-latest' args: '' - runs-on: ${{ matrix.settings.platform }} steps: - uses: actions/checkout@v4 + - name: setup node + uses: actions/setup-node@v4 + with: + node-version: lts/* + - name: install Rust stable + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.settings.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }} + - name: install npm dependencies + run: npm install + - uses: tauri-apps/tauri-action@v0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + releaseId: ${{ needs.create-release.outputs.release_id }} + args: ${{ matrix.settings.args }} - - name: install dependencies (ubuntu only) - if: matrix.settings.platform == 'ubuntu-22.04' + build-linux: + needs: create-release + permissions: + contents: write + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + + - name: install dependencies run: | sudo apt-get update sudo apt-get install -y \ @@ -35,8 +90,7 @@ jobs: gstreamer1.0-plugins-base gstreamer1.0-plugins-good \ gstreamer1.0-plugins-bad gstreamer1.0-libav - - name: install linuxdeploy gstreamer plugin (ubuntu only) - if: matrix.settings.platform == 'ubuntu-22.04' + - name: install linuxdeploy gstreamer plugin run: | wget -q "https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gstreamer/master/linuxdeploy-plugin-gstreamer.sh" \ -O /usr/local/bin/linuxdeploy-plugin-gstreamer.sh @@ -49,20 +103,32 @@ jobs: - name: install Rust stable uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.settings.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }} - name: install npm dependencies run: npm install - - uses: tauri-apps/tauri-action@v0 + - name: build + run: npm run tauri:build + env: + APPIMAGE_EXTRACT_AND_RUN: 1 + + - name: patch AppImage AppRun + run: | + APPIMAGE=$(find src-tauri/target/release/bundle/appimage -name "*.AppImage" -not -name "*.tar.gz" | head -1) + echo "Patching: $APPIMAGE" + APPIMAGE_EXTRACT_AND_RUN=1 ./"$APPIMAGE" --appimage-extract + sed -i '/^exec /i export WEBKIT_DISABLE_COMPOSITING_MODE=1\nexport WEBKIT_DISABLE_DMABUF_RENDERER=1' squashfs-root/AppRun + wget -q "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" \ + -O appimagetool + chmod +x appimagetool + APPIMAGE_EXTRACT_AND_RUN=1 ./appimagetool squashfs-root "$APPIMAGE" + rm -rf squashfs-root appimagetool + + - name: upload Linux artifacts env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - APPIMAGE_EXTRACT_AND_RUN: 1 - with: - tagName: app-v__VERSION__ - releaseName: 'Psysonic v__VERSION__' - releaseBody: 'See the assets to download this version and install.' - releaseDraft: false - prerelease: false - args: ${{ matrix.settings.args }} + run: | + VERSION=${{ needs.create-release.outputs.package_version }} + find src-tauri/target/release/bundle \ + \( -name "*.AppImage" -not -name "*.tar.gz" -o -name "*.deb" -o -name "*.rpm" \) \ + | xargs gh release upload "app-v${VERSION}" --clobber diff --git a/CHANGELOG.md b/CHANGELOG.md index 55993b3b..8b2635b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.0.4] - 2026-03-12 + +### Added +- **Album Downloads**: Support for downloading entire albums with real-time progress tracking. + +### Fixed +- **Linux GPU Compatibility**: Patched AppImage to disable DMABUF renderer, fixing EGL/GPU crashes on older hardware. +- **CI/CD Reliability**: Optimized release workflow with split jobs for better stability across platforms. + ## [1.0.3] - 2026-03-12 ### Fixed diff --git a/package.json b/package.json index c73256c8..ef389ef5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "psysonic", - "version": "1.0.3", + "version": "1.0.4", "private": true, "scripts": { "dev": "vite", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index e8096f94..bedc48b8 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -2732,7 +2732,7 @@ dependencies = [ [[package]] name = "psysonic" -version = "1.0.0" +version = "1.0.4" dependencies = [ "serde", "serde_json", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 9af87d8b..ca79274d 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "psysonic" -version = "1.0.3" +version = "1.0.4" description = "Psysonic Desktop Music Player" authors = [] license = "" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 1369ba1c..60f64149 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Psysonic", - "version": "1.0.3", + "version": "1.0.4", "identifier": "dev.psysonic.app", "build": { "beforeDevCommand": "npm run dev", diff --git a/src/pages/AlbumDetail.tsx b/src/pages/AlbumDetail.tsx index 2009e0cf..991dee0c 100644 --- a/src/pages/AlbumDetail.tsx +++ b/src/pages/AlbumDetail.tsx @@ -106,7 +106,7 @@ export default function AlbumDetail() { const [bio, setBio] = useState(null); const [bioOpen, setBioOpen] = useState(false); const [loading, setLoading] = useState(true); - const [downloading, setDownloading] = useState(false); + const [downloadProgress, setDownloadProgress] = useState(null); const [isStarred, setIsStarred] = useState(false); const [starredSongs, setStarredSongs] = useState>(new Set()); @@ -174,13 +174,33 @@ export default function AlbumDetail() { }; const handleDownload = async (albumName: string, albumId: string) => { - setDownloading(true); + setDownloadProgress(0); try { const url = buildDownloadUrl(albumId); const response = await fetch(url); if (!response.ok) throw new Error(`HTTP ${response.status}`); - const blob = await response.blob(); + const contentLength = response.headers.get('Content-Length'); + const total = contentLength ? parseInt(contentLength, 10) : 0; + const chunks: Uint8Array[] = []; + + if (total && response.body) { + const reader = response.body.getReader(); + let received = 0; + while (true) { + const { done, value } = await reader.read(); + if (done) break; + chunks.push(value); + received += value.length; + setDownloadProgress(Math.round((received / total) * 100)); + } + } else { + const buffer = await response.arrayBuffer() as ArrayBuffer; + chunks.push(new Uint8Array(buffer)); + setDownloadProgress(100); + } + + const blob = new Blob(chunks); if (auth.downloadFolder) { const buffer = await blob.arrayBuffer(); const path = await join(auth.downloadFolder, `${sanitizeFilename(albumName)}.zip`); @@ -197,8 +217,10 @@ export default function AlbumDetail() { } } catch (e) { console.error('Download failed:', e); + setDownloadProgress(null); } finally { - setDownloading(false); + // keep bar visible at 100% for 3 seconds so user sees completion + setTimeout(() => setDownloadProgress(null), 60000); } }; @@ -320,9 +342,19 @@ export default function AlbumDetail() { - + {downloadProgress !== null ? ( +
+ +
+
+
+ {downloadProgress}% +
+ ) : ( + + )}
diff --git a/src/styles/components.css b/src/styles/components.css index cec0a2e2..82301651 100644 --- a/src/styles/components.css +++ b/src/styles/components.css @@ -465,7 +465,39 @@ text-decoration: underline; } .album-detail-info { display: flex; gap: var(--space-3); color: var(--text-muted); font-size: 13px; margin: var(--space-2) 0 var(--space-4); } -.album-detail-actions { display: flex; gap: var(--space-3); } +.album-detail-actions { display: flex; gap: var(--space-3); align-items: center; } + +.download-progress-wrap { + display: flex; + align-items: center; + gap: var(--space-2); + padding: 0 var(--space-3); + height: 36px; + border-radius: var(--radius-md); + background: var(--surface-1); + border: 1px solid var(--border); + min-width: 180px; + color: var(--text-secondary); + font-size: 12px; +} +.download-progress-bar { + flex: 1; + height: 4px; + background: var(--surface-2); + border-radius: 2px; + overflow: hidden; +} +.download-progress-fill { + height: 100%; + background: linear-gradient(90deg, var(--ctp-mauve), var(--ctp-blue)); + border-radius: 2px; + transition: width 0.2s ease; +} +.download-progress-pct { + min-width: 28px; + text-align: right; + font-variant-numeric: tabular-nums; +} /* ─ Tracklist ─ */ .tracklist { padding: 0 var(--space-6) var(--space-6); }