Release v1.3.0: Multi-source downloads, audio analyzer resilience, mobile improvements
Major Features: - Multi-source download system (Soulseek/Lidarr with fallback) - Configurable enrichment speed control (1-5x) - Mobile touch drag support for seek sliders - iOS PWA media controls (Control Center, Lock Screen) - Artist name alias resolution via Last.fm - Circuit breaker pattern for audio analysis Critical Fixes: - Audio analyzer stability (non-ASCII, BrokenProcessPool, OOM) - Discovery system race conditions and import failures - Radio decade categorization using originalYear - LastFM API response normalization - Mood bucket infinite loop prevention Security: - Bull Board admin authentication - Lidarr webhook signature verification - JWT token expiration and refresh - Encryption key validation on startup Closes #2, #6, #9, #13, #21, #26, #31, #34, #35, #37, #40, #43
This commit is contained in:
@@ -12,6 +12,34 @@ export function DownloadPreferencesSection({
|
||||
settings,
|
||||
onUpdate,
|
||||
}: DownloadPreferencesSectionProps) {
|
||||
// Service configuration detection
|
||||
const isLidarrConfigured =
|
||||
settings.lidarrEnabled === true &&
|
||||
settings.lidarrUrl.trim() !== "" &&
|
||||
settings.lidarrApiKey.trim() !== "";
|
||||
|
||||
const isSoulseekConfigured =
|
||||
settings.soulseekUsername.trim() !== "" &&
|
||||
settings.soulseekPassword.trim() !== "";
|
||||
|
||||
const areBothServicesConfigured = isLidarrConfigured && isSoulseekConfigured;
|
||||
const isDisabled = !areBothServicesConfigured;
|
||||
|
||||
// Dynamic fallback options based on primary source
|
||||
const getFallbackOptions = () => {
|
||||
if (settings.downloadSource === "soulseek") {
|
||||
return [
|
||||
{ value: "none", label: "Skip track" },
|
||||
{ value: "lidarr", label: "Download full album via Lidarr" },
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
{ value: "none", label: "Skip album" },
|
||||
{ value: "soulseek", label: "Try Soulseek for individual tracks" },
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
id="download-preferences"
|
||||
@@ -20,42 +48,80 @@ export function DownloadPreferencesSection({
|
||||
>
|
||||
<SettingsRow
|
||||
label="Primary Download Source"
|
||||
description="Choose how to download music for imported playlists"
|
||||
description={
|
||||
isDisabled
|
||||
? "Requires both Soulseek and Lidarr to be configured"
|
||||
: "Choose how to download music for imported playlists"
|
||||
}
|
||||
>
|
||||
<SettingsSelect
|
||||
value={settings.downloadSource || "soulseek"}
|
||||
onChange={(v) =>
|
||||
onUpdate({ downloadSource: v as "soulseek" | "lidarr" })
|
||||
onUpdate({
|
||||
downloadSource: v as "soulseek" | "lidarr",
|
||||
primaryFailureFallback: "none"
|
||||
})
|
||||
}
|
||||
options={[
|
||||
{ value: "soulseek", label: "Soulseek (Per-track)" },
|
||||
{ value: "lidarr", label: "Lidarr (Full albums)" },
|
||||
]}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
</SettingsRow>
|
||||
|
||||
{settings.downloadSource === "soulseek" && (
|
||||
<SettingsRow
|
||||
label="When Soulseek Fails"
|
||||
description="What to do if a track can't be found on Soulseek"
|
||||
>
|
||||
<SettingsSelect
|
||||
value={settings.soulseekFallback || "none"}
|
||||
onChange={(v) =>
|
||||
onUpdate({
|
||||
soulseekFallback: v as "none" | "lidarr",
|
||||
})
|
||||
}
|
||||
options={[
|
||||
{ value: "none", label: "Skip track" },
|
||||
{
|
||||
value: "lidarr",
|
||||
label: "Download full album via Lidarr",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</SettingsRow>
|
||||
)}
|
||||
<SettingsRow
|
||||
label={
|
||||
settings.downloadSource === "soulseek"
|
||||
? "When Soulseek Fails"
|
||||
: "When Lidarr Fails"
|
||||
}
|
||||
description={
|
||||
isDisabled
|
||||
? "Requires both Soulseek and Lidarr to be configured"
|
||||
: settings.downloadSource === "soulseek"
|
||||
? "What to do if a track can't be found on Soulseek"
|
||||
: "What to do if an album can't be found on Lidarr"
|
||||
}
|
||||
>
|
||||
<SettingsSelect
|
||||
value={settings.primaryFailureFallback || "none"}
|
||||
onChange={(v) =>
|
||||
onUpdate({
|
||||
primaryFailureFallback: v as "none" | "lidarr" | "soulseek",
|
||||
})
|
||||
}
|
||||
options={getFallbackOptions()}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
label="Soulseek Concurrent Downloads"
|
||||
description="Number of simultaneous downloads when using Soulseek (1-10)"
|
||||
>
|
||||
<SettingsSelect
|
||||
value={settings.soulseekConcurrentDownloads?.toString() || "4"}
|
||||
onChange={(v) =>
|
||||
onUpdate({
|
||||
soulseekConcurrentDownloads: parseInt(v),
|
||||
})
|
||||
}
|
||||
options={[
|
||||
{ value: "1", label: "1" },
|
||||
{ value: "2", label: "2" },
|
||||
{ value: "3", label: "3" },
|
||||
{ value: "4", label: "4 (Default)" },
|
||||
{ value: "5", label: "5" },
|
||||
{ value: "6", label: "6" },
|
||||
{ value: "7", label: "7" },
|
||||
{ value: "8", label: "8" },
|
||||
{ value: "9", label: "9" },
|
||||
{ value: "10", label: "10" },
|
||||
]}
|
||||
disabled={!isSoulseekConfigured}
|
||||
/>
|
||||
</SettingsRow>
|
||||
</SettingsSection>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user