diff --git a/allstarr/Services/Common/BaseDownloadService.cs b/allstarr/Services/Common/BaseDownloadService.cs index 995ef39..ef304fd 100644 --- a/allstarr/Services/Common/BaseDownloadService.cs +++ b/allstarr/Services/Common/BaseDownloadService.cs @@ -5,6 +5,7 @@ using allstarr.Models.Search; using allstarr.Models.Subsonic; using allstarr.Services.Local; using allstarr.Services.Subsonic; +using System.Collections.Concurrent; using TagLib; using IOFile = System.IO.File; @@ -27,7 +28,7 @@ public abstract class BaseDownloadService : IDownloadService protected readonly string DownloadPath; protected readonly string CachePath; - protected readonly Dictionary ActiveDownloads = new(); + protected readonly ConcurrentDictionary ActiveDownloads = new(); protected readonly SemaphoreSlim DownloadLock = new(1, 1); /// @@ -298,6 +299,14 @@ public abstract class BaseDownloadService : IDownloadService song.LocalPath = localPath; + // Clean up completed download from tracking after a short delay + _ = Task.Run(async () => + { + await Task.Delay(TimeSpan.FromMinutes(5)); // Keep for 5 minutes for status checks + ActiveDownloads.TryRemove(songId, out _); + Logger.LogDebug("Cleaned up completed download tracking for {SongId}", songId); + }); + // Register BEFORE releasing lock to prevent race conditions (both cache and download modes) await LocalLibraryService.RegisterDownloadedSongAsync(song, localPath); @@ -360,6 +369,14 @@ public abstract class BaseDownloadService : IDownloadService { downloadInfo.Status = DownloadStatus.Failed; downloadInfo.ErrorMessage = ex.Message; + + // Clean up failed download from tracking after a short delay + _ = Task.Run(async () => + { + await Task.Delay(TimeSpan.FromMinutes(2)); // Keep for 2 minutes for error reporting + ActiveDownloads.TryRemove(songId, out _); + Logger.LogDebug("Cleaned up failed download tracking for {SongId}", songId); + }); } Logger.LogError(ex, "Download failed for {SongId}", songId); throw;