fix: resolve nested download paths for covers and lyrics (#219)

This commit fixes an issue where cover art and lyrics files were being saved in deeply nested directories (e.g. Album/Artist/Album/file) instead of the correct Artist/Album/file path. It adds an isAlbum flag to the frontend hooks to prevent redundant path construction when downloading in an album context.

Co-authored-by: Harley <git@haileywelsh.me>
This commit is contained in:
Harley Welsh
2026-01-08 05:34:29 +00:00
committed by GitHub
parent ae8b610462
commit 1345ac25f4
4 changed files with 62 additions and 15 deletions
+39
View File
@@ -0,0 +1,39 @@
# Development Guide
## Prerequisites
Before running the application locally, ensure you have the following installed:
1. **Go** (v1.23+ recommended)
2. **Node.js** (v16+ recommended)
3. **Wails CLI**
### Installing Wails
Since you already have Go installed, you can install the Wails CLI by running:
```bash
go install github.com/wailsapp/wails/v2/cmd/wails@latest
```
Ensure that your `go/bin` directory is in your PATH. You can check if it's installed by running `wails version`.
## Running the Application
To run the application in development mode (with hot reloading for both frontend and backend):
```bash
wails dev
```
This will compiles the application and open it in a window. It also starts a browser-based version at http://localhost:34115.
## Building for Production
To create a production build (Application.app):
```bash
wails build
```
The output will be in the `build/bin` directory.
+5 -5
View File
@@ -357,14 +357,14 @@ function App() {
onToggleSelectAll={toggleSelectAll} onToggleSelectAll={toggleSelectAll}
onDownloadTrack={download.handleDownloadTrack} onDownloadTrack={download.handleDownloadTrack}
onDownloadLyrics={(spotifyId, name, artists, albumName, _folderName, _isArtistDiscography, position, albumArtist, releaseDate, discNumber) => onDownloadLyrics={(spotifyId, name, artists, albumName, _folderName, _isArtistDiscography, position, albumArtist, releaseDate, discNumber) =>
lyrics.handleDownloadLyrics(spotifyId, name, artists, albumName, album_info.name, position, albumArtist, releaseDate, discNumber) lyrics.handleDownloadLyrics(spotifyId, name, artists, albumName, album_info.name, position, albumArtist, releaseDate, discNumber, true)
} }
onDownloadCover={(coverUrl, trackName, artistName, albumName, _folderName, _isArtistDiscography, position, trackId, albumArtist, releaseDate, discNumber) => onDownloadCover={(coverUrl, trackName, artistName, albumName, _folderName, _isArtistDiscography, position, trackId, albumArtist, releaseDate, discNumber) =>
cover.handleDownloadCover(coverUrl, trackName, artistName, albumName, album_info.name, position, trackId, albumArtist, releaseDate, discNumber) cover.handleDownloadCover(coverUrl, trackName, artistName, albumName, album_info.name, position, trackId, albumArtist, releaseDate, discNumber, true)
} }
onCheckAvailability={availability.checkAvailability} onCheckAvailability={availability.checkAvailability}
onDownloadAllLyrics={() => lyrics.handleDownloadAllLyrics(track_list, album_info.name)} onDownloadAllLyrics={() => lyrics.handleDownloadAllLyrics(track_list, album_info.name, undefined, true)}
onDownloadAllCovers={() => cover.handleDownloadAllCovers(track_list, album_info.name)} onDownloadAllCovers={() => cover.handleDownloadAllCovers(track_list, album_info.name, true)}
onDownloadAll={() => download.handleDownloadAll(track_list, undefined, true)} onDownloadAll={() => download.handleDownloadAll(track_list, undefined, true)}
onDownloadSelected={() => onDownloadSelected={() =>
download.handleDownloadSelected(selectedTracks, track_list, undefined, true) download.handleDownloadSelected(selectedTracks, track_list, undefined, true)
@@ -684,7 +684,7 @@ function App() {
<div className="min-h-screen bg-background flex flex-col"> <div className="min-h-screen bg-background flex flex-col">
<TitleBar /> <TitleBar />
<Sidebar currentPage={currentPage} onPageChange={setCurrentPage} /> <Sidebar currentPage={currentPage} onPageChange={setCurrentPage} />
{/* Main content area with sidebar offset */} {/* Main content area with sidebar offset */}
<div className="flex-1 ml-14 mt-10 p-4 md:p-8"> <div className="flex-1 ml-14 mt-10 p-4 md:p-8">
<div className="max-w-4xl mx-auto space-y-6"> <div className="max-w-4xl mx-auto space-y-6">
+8 -4
View File
@@ -26,7 +26,8 @@ export function useCover() {
trackId?: string, trackId?: string,
albumArtist?: string, albumArtist?: string,
releaseDate?: string, releaseDate?: string,
discNumber?: number discNumber?: number,
isAlbum?: boolean
) => { ) => {
if (!coverUrl) { if (!coverUrl) {
toast.error("No cover URL found for this track"); toast.error("No cover URL found for this track");
@@ -54,7 +55,8 @@ export function useCover() {
}; };
// For playlist/discography, prepend the folder name // For playlist/discography, prepend the folder name
if (playlistName) { // Only do this if it's NOT an album download, to avoid double nesting (AlbumName/Artist/AlbumName)
if (playlistName && !isAlbum) {
outputDir = joinPath(os, outputDir, sanitizePath(playlistName.replace(/\//g, " "), os)); outputDir = joinPath(os, outputDir, sanitizePath(playlistName.replace(/\//g, " "), os));
} }
@@ -113,7 +115,8 @@ export function useCover() {
const handleDownloadAllCovers = async ( const handleDownloadAllCovers = async (
tracks: TrackMetadata[], tracks: TrackMetadata[],
playlistName?: string playlistName?: string,
isAlbum?: boolean // Add isAlbum parameter
) => { ) => {
if (tracks.length === 0) { if (tracks.length === 0) {
toast.error("No tracks to download covers"); toast.error("No tracks to download covers");
@@ -166,7 +169,8 @@ export function useCover() {
}; };
// For playlist/discography, prepend the folder name // For playlist/discography, prepend the folder name
if (playlistName) { // Only do this if it's NOT an album download
if (playlistName && !isAlbum) {
outputDir = joinPath(os, outputDir, sanitizePath(playlistName.replace(/\//g, " "), os)); outputDir = joinPath(os, outputDir, sanitizePath(playlistName.replace(/\//g, " "), os));
} }
+10 -6
View File
@@ -24,7 +24,8 @@ export function useLyrics() {
position?: number, position?: number,
albumArtist?: string, albumArtist?: string,
releaseDate?: string, releaseDate?: string,
discNumber?: number discNumber?: number,
isAlbum?: boolean // Add isAlbum parameter
) => { ) => {
if (!spotifyId) { if (!spotifyId) {
toast.error("No Spotify ID found for this track"); toast.error("No Spotify ID found for this track");
@@ -51,7 +52,8 @@ export function useLyrics() {
}; };
// For playlist/discography, prepend the folder name // For playlist/discography, prepend the folder name
if (playlistName) { // Only do this if it's NOT an album download
if (playlistName && !isAlbum) {
outputDir = joinPath(os, outputDir, sanitizePath(playlistName.replace(/\//g, " "), os)); outputDir = joinPath(os, outputDir, sanitizePath(playlistName.replace(/\//g, " "), os));
} }
@@ -113,7 +115,8 @@ export function useLyrics() {
const handleDownloadAllLyrics = async ( const handleDownloadAllLyrics = async (
tracks: TrackMetadata[], tracks: TrackMetadata[],
playlistName?: string, playlistName?: string,
_isArtistDiscography?: boolean _isArtistDiscography?: boolean,
isAlbum?: boolean // Add isAlbum parameter
) => { ) => {
const tracksWithSpotifyId = tracks.filter((track) => track.spotify_id); const tracksWithSpotifyId = tracks.filter((track) => track.spotify_id);
@@ -150,12 +153,12 @@ export function useLyrics() {
// Replace forward slashes in template data values to prevent them from being interpreted as path separators // Replace forward slashes in template data values to prevent them from being interpreted as path separators
const placeholder = "__SLASH_PLACEHOLDER__"; const placeholder = "__SLASH_PLACEHOLDER__";
// Determine if we should use album track number or sequential position // Determine if we should use album track number or sequential position
const useAlbumTrackNumber = settings.folderTemplate?.includes("{album}") || false; const useAlbumTrackNumber = settings.folderTemplate?.includes("{album}") || false;
// Use track.track_number for album context, otherwise use sequential position (consistent with track download) // Use track.track_number for album context, otherwise use sequential position (consistent with track download)
const trackPosition = useAlbumTrackNumber ? (track.track_number || i + 1) : (i + 1); const trackPosition = useAlbumTrackNumber ? (track.track_number || i + 1) : (i + 1);
// Build output path using template system // Build output path using template system
const templateData: TemplateData = { const templateData: TemplateData = {
artist: track.artists?.replace(/\//g, placeholder), artist: track.artists?.replace(/\//g, placeholder),
@@ -166,7 +169,8 @@ export function useLyrics() {
}; };
// For playlist/discography, prepend the folder name // For playlist/discography, prepend the folder name
if (playlistName) { // Only do this if it's NOT an album download
if (playlistName && !isAlbum) {
outputDir = joinPath(os, outputDir, sanitizePath(playlistName.replace(/\//g, " "), os)); outputDir = joinPath(os, outputDir, sanitizePath(playlistName.replace(/\//g, " "), os));
} }