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:
@@ -370,6 +370,29 @@ function DownloadJobItem({
|
||||
}
|
||||
};
|
||||
|
||||
const getSourceColor = () => {
|
||||
if (!job.metadata?.currentSource) return "text-white/60";
|
||||
switch (job.metadata.currentSource) {
|
||||
case "lidarr":
|
||||
return "text-purple-400";
|
||||
case "soulseek":
|
||||
return "text-teal-400";
|
||||
default:
|
||||
return "text-white/60";
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusText = () => {
|
||||
if (job.metadata?.statusText) {
|
||||
return job.metadata.statusText;
|
||||
}
|
||||
// Fallback for backward compatibility
|
||||
if (job.status === "processing" || job.status === "pending") {
|
||||
return "Processing";
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
setIsDeleting(true);
|
||||
@@ -397,7 +420,7 @@ function DownloadJobItem({
|
||||
<p className="text-sm font-medium text-white truncate">
|
||||
{job.subject}
|
||||
</p>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<div className="flex items-center gap-2 mt-1 flex-wrap">
|
||||
<span
|
||||
className={cn(
|
||||
"text-xs font-medium capitalize",
|
||||
@@ -406,6 +429,19 @@ function DownloadJobItem({
|
||||
>
|
||||
{job.status}
|
||||
</span>
|
||||
{getStatusText() && (
|
||||
<>
|
||||
<span className="text-xs text-white/40">•</span>
|
||||
<span
|
||||
className={cn(
|
||||
"text-xs font-medium",
|
||||
getSourceColor()
|
||||
)}
|
||||
>
|
||||
{getStatusText()}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
<span className="text-xs text-white/40">•</span>
|
||||
<span className="text-xs text-white/40 capitalize">
|
||||
{job.type}
|
||||
@@ -457,13 +493,48 @@ function DownloadJobItemCompact({
|
||||
}
|
||||
};
|
||||
|
||||
const getSourceColor = () => {
|
||||
if (!job.metadata?.currentSource) return "text-white/60";
|
||||
switch (job.metadata.currentSource) {
|
||||
case "lidarr":
|
||||
return "text-purple-400";
|
||||
case "soulseek":
|
||||
return "text-teal-400";
|
||||
default:
|
||||
return "text-white/60";
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusText = () => {
|
||||
if (job.metadata?.statusText) {
|
||||
return job.metadata.statusText;
|
||||
}
|
||||
// Fallback for backward compatibility
|
||||
if (job.status === "processing" || job.status === "pending") {
|
||||
return "Processing";
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="px-3 py-2 flex items-center gap-2">
|
||||
<div className="flex-shrink-0">{getStatusIcon()}</div>
|
||||
<p className="flex-1 text-xs font-medium text-white truncate">
|
||||
{job.subject}
|
||||
</p>
|
||||
<span className="text-[10px] text-white/40 capitalize">
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-xs font-medium text-white truncate">
|
||||
{job.subject}
|
||||
</p>
|
||||
{getStatusText() && (
|
||||
<p
|
||||
className={cn(
|
||||
"text-[10px] font-medium",
|
||||
getSourceColor()
|
||||
)}
|
||||
>
|
||||
{getStatusText()}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-[10px] text-white/40 capitalize shrink-0">
|
||||
{job.status}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user