fix: prevent duplicate downloads from concurrent stream requests

This commit is contained in:
V1ck3s
2026-01-12 18:40:45 +01:00
committed by Vickes
parent 1d4c46b4f3
commit 7a08f15523

View File

@@ -139,6 +139,11 @@ public abstract class BaseDownloadService : IDownloadService
var songId = $"ext-{externalProvider}-{externalId}"; var songId = $"ext-{externalProvider}-{externalId}";
var isCache = SubsonicSettings.StorageMode == StorageMode.Cache; var isCache = SubsonicSettings.StorageMode == StorageMode.Cache;
// Acquire lock BEFORE checking existence to prevent race conditions with concurrent requests
await DownloadLock.WaitAsync(cancellationToken);
try
{
// Check if already downloaded (skip for cache mode as we want to check cache folder) // Check if already downloaded (skip for cache mode as we want to check cache folder)
if (!isCache) if (!isCache)
{ {
@@ -165,7 +170,10 @@ public abstract class BaseDownloadService : IDownloadService
// Check if download in progress // Check if download in progress
if (ActiveDownloads.TryGetValue(songId, out var activeDownload) && activeDownload.Status == DownloadStatus.InProgress) if (ActiveDownloads.TryGetValue(songId, out var activeDownload) && activeDownload.Status == DownloadStatus.InProgress)
{ {
Logger.LogInformation("Download already in progress for {SongId}", songId); Logger.LogInformation("Download already in progress for {SongId}, waiting...", songId);
// Release lock while waiting
DownloadLock.Release();
while (ActiveDownloads.TryGetValue(songId, out activeDownload) && activeDownload.Status == DownloadStatus.InProgress) while (ActiveDownloads.TryGetValue(songId, out activeDownload) && activeDownload.Status == DownloadStatus.InProgress)
{ {
await Task.Delay(500, cancellationToken); await Task.Delay(500, cancellationToken);
@@ -179,9 +187,6 @@ public abstract class BaseDownloadService : IDownloadService
throw new Exception(activeDownload?.ErrorMessage ?? "Download failed"); throw new Exception(activeDownload?.ErrorMessage ?? "Download failed");
} }
await DownloadLock.WaitAsync(cancellationToken);
try
{
// Get metadata // Get metadata
// In Album mode, fetch the full album first to ensure AlbumArtist is correctly set // In Album mode, fetch the full album first to ensure AlbumArtist is correctly set
Song? song = null; Song? song = null;
@@ -227,8 +232,6 @@ public abstract class BaseDownloadService : IDownloadService
}; };
ActiveDownloads[songId] = downloadInfo; ActiveDownloads[songId] = downloadInfo;
try
{
var localPath = await DownloadTrackAsync(externalId, song, cancellationToken); var localPath = await DownloadTrackAsync(externalId, song, cancellationToken);
downloadInfo.Status = DownloadStatus.Completed; downloadInfo.Status = DownloadStatus.Completed;
@@ -275,13 +278,15 @@ public abstract class BaseDownloadService : IDownloadService
return localPath; return localPath;
} }
catch (Exception ex) catch (Exception ex)
{
if (ActiveDownloads.TryGetValue(songId, out var downloadInfo))
{ {
downloadInfo.Status = DownloadStatus.Failed; downloadInfo.Status = DownloadStatus.Failed;
downloadInfo.ErrorMessage = ex.Message; downloadInfo.ErrorMessage = ex.Message;
}
Logger.LogError(ex, "Download failed for {SongId}", songId); Logger.LogError(ex, "Download failed for {SongId}", songId);
throw; throw;
} }
}
finally finally
{ {
DownloadLock.Release(); DownloadLock.Release();