fix(purchases): restructure download paths to Artist/Album [FORMAT][Quality]/track
Embeds quality in album folder name instead of subfolder, so each quality variant appears as a separate album in the local library. Adds download error toast and fixes format labels to bracket notation. Closes #116
This commit is contained in:
7 files changed
+32
-33
No files matched your search
@@ -10614,7 +10614,7 @@ fn v2_purchase_target_path(
|
||||
ext: &str,
|
||||
) -> PathBuf {
|
||||
let artist_dir = crate::offline_cache::metadata::sanitize_filename(artist_name);
|
||||
let album_dir = crate::offline_cache::metadata::sanitize_filename(album_title);
|
||||
let album_clean = crate::offline_cache::metadata::sanitize_filename(album_title);
|
||||
let title_clean = crate::offline_cache::metadata::sanitize_filename(track_title);
|
||||
|
||||
let file_name = if track_number > 0 {
|
||||
@@ -10623,13 +10623,18 @@ fn v2_purchase_target_path(
|
||||
format!("{}.{}", title_clean, ext)
|
||||
};
|
||||
|
||||
let mut path = PathBuf::from(destination)
|
||||
// Embed quality in album folder name: "Album [FLAC][24-bit,96kHz]"
|
||||
let album_dir = if !quality_dir.is_empty() {
|
||||
let quality_clean = crate::offline_cache::metadata::sanitize_filename(quality_dir);
|
||||
format!("{} {}", album_clean, quality_clean)
|
||||
} else {
|
||||
album_clean
|
||||
};
|
||||
|
||||
PathBuf::from(destination)
|
||||
.join(artist_dir)
|
||||
.join(album_dir);
|
||||
if !quality_dir.is_empty() {
|
||||
path = path.join(crate::offline_cache::metadata::sanitize_filename(quality_dir));
|
||||
}
|
||||
path.join(file_name)
|
||||
.join(album_dir)
|
||||
.join(file_name)
|
||||
}
|
||||
|
||||
fn v2_apply_purchase_download_flags(
|
||||
@@ -11000,7 +11005,7 @@ pub async fn v2_purchases_get_formats(
|
||||
if album.hires && album.maximum_sampling_rate.unwrap_or(0.0) > 96.0 {
|
||||
formats.push(V2PurchaseFormatOption {
|
||||
id: 27,
|
||||
label: "FLAC 24-bit / 192 kHz".to_string(),
|
||||
label: "[FLAC][24-bit,192kHz]".to_string(),
|
||||
bit_depth: Some(24),
|
||||
sampling_rate: Some(192.0),
|
||||
});
|
||||
@@ -11009,7 +11014,7 @@ pub async fn v2_purchases_get_formats(
|
||||
if album.hires {
|
||||
formats.push(V2PurchaseFormatOption {
|
||||
id: 7,
|
||||
label: "FLAC 24-bit / 96 kHz".to_string(),
|
||||
label: "[FLAC][24-bit,96kHz]".to_string(),
|
||||
bit_depth: Some(24),
|
||||
sampling_rate: Some(96.0),
|
||||
});
|
||||
@@ -11017,14 +11022,14 @@ pub async fn v2_purchases_get_formats(
|
||||
|
||||
formats.push(V2PurchaseFormatOption {
|
||||
id: 6,
|
||||
label: "FLAC 16-bit / 44.1 kHz".to_string(),
|
||||
label: "[FLAC][16-bit,44.1kHz]".to_string(),
|
||||
bit_depth: Some(16),
|
||||
sampling_rate: Some(44.1),
|
||||
});
|
||||
|
||||
formats.push(V2PurchaseFormatOption {
|
||||
id: 5,
|
||||
label: "MP3 320 kbps".to_string(),
|
||||
label: "[MP3][320kbps]".to_string(),
|
||||
bit_depth: None,
|
||||
sampling_rate: None,
|
||||
});
|
||||
|
||||
@@ -436,6 +436,7 @@
|
||||
formatPickerAnchor = { top: rect.bottom + 4, right: window.innerWidth - rect.right };
|
||||
} catch (err) {
|
||||
console.error('Track download error:', err);
|
||||
showToast($t('purchases.errors.downloadFailed'), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1865,7 +1866,7 @@
|
||||
}
|
||||
|
||||
.track-row.downloaded {
|
||||
opacity: 0.6;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
/* Format picker popup */
|
||||
|
||||
@@ -1719,7 +1719,8 @@
|
||||
"regionNotice": "Die Verfügbarkeit von Käufen hängt von deinem Qobuz-Konto und deiner Region ab. Wenn es nicht verfügbar ist, kann das erwartet sein.",
|
||||
"errors": {
|
||||
"noAlbum": "Download nicht moeglich: Titel hat kein zugehoeriges Album.",
|
||||
"noFormats": "Keine Download-Formate fuer diesen Titel verfuegbar."
|
||||
"noFormats": "Keine Download-Formate fuer diesen Titel verfuegbar.",
|
||||
"downloadFailed": "Download konnte nicht gestartet werden. Bitte erneut versuchen."
|
||||
}
|
||||
},
|
||||
"playlistManager": {
|
||||
|
||||
@@ -1900,7 +1900,8 @@
|
||||
"regionNotice": "Purchases availability depends on your Qobuz account and region. If unavailable, this may be expected.",
|
||||
"errors": {
|
||||
"noAlbum": "Cannot download: track has no associated album.",
|
||||
"noFormats": "No download formats available for this track."
|
||||
"noFormats": "No download formats available for this track.",
|
||||
"downloadFailed": "Failed to start download. Please try again."
|
||||
}
|
||||
},
|
||||
"yourMixes": {
|
||||
|
||||
@@ -1900,7 +1900,8 @@
|
||||
"regionNotice": "La disponibilidad de Compras depende de tu cuenta y region de Qobuz. Si no aparece disponible, puede ser esperado.",
|
||||
"errors": {
|
||||
"noAlbum": "No se puede descargar: la pista no tiene album asociado.",
|
||||
"noFormats": "No hay formatos de descarga disponibles para esta pista."
|
||||
"noFormats": "No hay formatos de descarga disponibles para esta pista.",
|
||||
"downloadFailed": "Error al iniciar la descarga. Intenta de nuevo."
|
||||
}
|
||||
},
|
||||
"yourMixes": {
|
||||
|
||||
@@ -1719,7 +1719,8 @@
|
||||
"regionNotice": "La disponibilité des achats dépend de votre compte Qobuz et de votre région. Si ce n'est pas disponible, cela peut être normal.",
|
||||
"errors": {
|
||||
"noAlbum": "Telechargement impossible : la piste n'a pas d'album associe.",
|
||||
"noFormats": "Aucun format de telechargement disponible pour cette piste."
|
||||
"noFormats": "Aucun format de telechargement disponible pour cette piste.",
|
||||
"downloadFailed": "Impossible de demarrer le telechargement. Veuillez reessayer."
|
||||
}
|
||||
},
|
||||
"playlistManager": {
|
||||
|
||||
@@ -81,25 +81,14 @@ export function startAlbumDownload(
|
||||
|
||||
/**
|
||||
* Extract the album-level folder from a downloaded file path.
|
||||
* Download structure: destination/artist/album/[qualityDir/]track.ext
|
||||
* We want: destination/artist/album/
|
||||
* Download structure: destination/artist/album [quality]/track.ext
|
||||
* We want: destination/artist/album [quality]/
|
||||
*/
|
||||
function albumFolderFromFilePath(filePath: string, hasQualityDir: boolean): string {
|
||||
// Remove filename
|
||||
function albumFolderFromFilePath(filePath: string): string {
|
||||
// Remove filename to get the album directory
|
||||
let lastSep = filePath.lastIndexOf('/');
|
||||
if (lastSep < 0) lastSep = filePath.lastIndexOf('\\');
|
||||
let dir = lastSep >= 0 ? filePath.substring(0, lastSep) : filePath;
|
||||
|
||||
// If there's a quality subdirectory, go up one more level
|
||||
if (hasQualityDir) {
|
||||
let prevSep = dir.lastIndexOf('/');
|
||||
if (prevSep < 0) prevSep = dir.lastIndexOf('\\');
|
||||
if (prevSep >= 0) {
|
||||
dir = dir.substring(0, prevSep);
|
||||
}
|
||||
}
|
||||
|
||||
return dir;
|
||||
return lastSep >= 0 ? filePath.substring(0, lastSep) : filePath;
|
||||
}
|
||||
|
||||
async function executeAlbumDownload(
|
||||
@@ -144,7 +133,7 @@ async function executeAlbumDownload(
|
||||
// so "Add to Library" adds only this album, not the entire root.
|
||||
if (!albumFolderResolved) {
|
||||
albumFolderResolved = true;
|
||||
const albumFolder = albumFolderFromFilePath(filePath, qualityDir.length > 0);
|
||||
const albumFolder = albumFolderFromFilePath(filePath);
|
||||
updateAlbumState(albumId, (state) => ({
|
||||
...state,
|
||||
destination: albumFolder,
|
||||
|
||||
Reference in new issue
Block a user