Fix download racing, cache cleanup, and WebUI storage mode display

- Replace endpoint racing with round-robin fallback for downloads to reduce CPU usage and prevent cancellation errors
- Fix cache cleanup to use LastWriteTimeUtc instead of unreliable LastAccessTimeUtc
- Add storage mode, cache duration, and download mode to admin config endpoint
- Show actual download path based on storage mode (cache/Music vs downloads)
This commit is contained in:
2026-02-07 16:45:28 -05:00
parent f741cc5297
commit b1769a35bf
3 changed files with 28 additions and 21 deletions

View File

@@ -28,6 +28,7 @@ public class AdminController : ControllerBase
private readonly SpotifyApiSettings _spotifyApiSettings; private readonly SpotifyApiSettings _spotifyApiSettings;
private readonly SpotifyImportSettings _spotifyImportSettings; private readonly SpotifyImportSettings _spotifyImportSettings;
private readonly JellyfinSettings _jellyfinSettings; private readonly JellyfinSettings _jellyfinSettings;
private readonly SubsonicSettings _subsonicSettings;
private readonly DeezerSettings _deezerSettings; private readonly DeezerSettings _deezerSettings;
private readonly QobuzSettings _qobuzSettings; private readonly QobuzSettings _qobuzSettings;
private readonly SquidWTFSettings _squidWtfSettings; private readonly SquidWTFSettings _squidWtfSettings;
@@ -52,6 +53,7 @@ public class AdminController : ControllerBase
IOptions<SpotifyApiSettings> spotifyApiSettings, IOptions<SpotifyApiSettings> spotifyApiSettings,
IOptions<SpotifyImportSettings> spotifyImportSettings, IOptions<SpotifyImportSettings> spotifyImportSettings,
IOptions<JellyfinSettings> jellyfinSettings, IOptions<JellyfinSettings> jellyfinSettings,
IOptions<SubsonicSettings> subsonicSettings,
IOptions<DeezerSettings> deezerSettings, IOptions<DeezerSettings> deezerSettings,
IOptions<QobuzSettings> qobuzSettings, IOptions<QobuzSettings> qobuzSettings,
IOptions<SquidWTFSettings> squidWtfSettings, IOptions<SquidWTFSettings> squidWtfSettings,
@@ -69,6 +71,7 @@ public class AdminController : ControllerBase
_spotifyApiSettings = spotifyApiSettings.Value; _spotifyApiSettings = spotifyApiSettings.Value;
_spotifyImportSettings = spotifyImportSettings.Value; _spotifyImportSettings = spotifyImportSettings.Value;
_jellyfinSettings = jellyfinSettings.Value; _jellyfinSettings = jellyfinSettings.Value;
_subsonicSettings = subsonicSettings.Value;
_deezerSettings = deezerSettings.Value; _deezerSettings = deezerSettings.Value;
_qobuzSettings = qobuzSettings.Value; _qobuzSettings = qobuzSettings.Value;
_squidWtfSettings = squidWtfSettings.Value; _squidWtfSettings = squidWtfSettings.Value;
@@ -1408,8 +1411,13 @@ public class AdminController : ControllerBase
}, },
library = new library = new
{ {
downloadPath = _configuration["Library:DownloadPath"] ?? "./downloads", downloadPath = _subsonicSettings.StorageMode == StorageMode.Cache
keptPath = _configuration["Library:KeptPath"] ?? "/app/kept" ? Path.Combine("cache", "Music")
: (_configuration["Library:DownloadPath"] ?? "./downloads"),
keptPath = _configuration["Library:KeptPath"] ?? "/app/kept",
storageMode = _subsonicSettings.StorageMode.ToString(),
cacheDurationHours = _subsonicSettings.CacheDurationHours,
downloadMode = _subsonicSettings.DownloadMode.ToString()
}, },
deezer = new deezer = new
{ {

View File

@@ -94,16 +94,16 @@ public class CacheCleanupService : BackgroundService
{ {
var fileInfo = new FileInfo(filePath); var fileInfo = new FileInfo(filePath);
// Use last access time to determine if file should be deleted // Use last write time (when file was created/downloaded) to determine if file should be deleted
// This gets updated when a cached file is streamed // LastAccessTime is unreliable on many filesystems (noatime mount option)
if (fileInfo.LastAccessTimeUtc < cutoffTime) if (fileInfo.LastWriteTimeUtc < cutoffTime)
{ {
var size = fileInfo.Length; var size = fileInfo.Length;
File.Delete(filePath); File.Delete(filePath);
deletedCount++; deletedCount++;
totalSize += size; totalSize += size;
_logger.LogDebug("Deleted cached file: {Path} (last accessed: {LastAccess})", _logger.LogDebug("Deleted cached file: {Path} (age: {Age:F1} hours)",
filePath, fileInfo.LastAccessTimeUtc); filePath, (DateTime.UtcNow - fileInfo.LastWriteTimeUtc).TotalHours);
} }
} }
catch (Exception ex) catch (Exception ex)

View File

@@ -135,10 +135,10 @@ public class SquidWTFDownloadService : BaseDownloadService
// Resolve unique path if file already exists // Resolve unique path if file already exists
outputPath = PathHelper.ResolveUniquePath(outputPath); outputPath = PathHelper.ResolveUniquePath(outputPath);
// Race all endpoints to download from the fastest one // Use round-robin with fallback for downloads to reduce CPU usage
Logger.LogInformation("🏁 Racing {Count} endpoints for fastest download", _fallbackHelper.EndpointCount); Logger.LogDebug("Using round-robin endpoint selection for download");
var response = await _fallbackHelper.RaceAllEndpointsAsync(async (baseUrl, ct) => var response = await _fallbackHelper.TryWithFallbackAsync(async (baseUrl) =>
{ {
// Map quality settings to Tidal's quality levels per hifi-api spec // Map quality settings to Tidal's quality levels per hifi-api spec
var quality = _squidwtfSettings.Quality?.ToUpperInvariant() switch var quality = _squidwtfSettings.Quality?.ToUpperInvariant() switch
@@ -154,10 +154,10 @@ public class SquidWTFDownloadService : BaseDownloadService
var url = $"{baseUrl}/track/?id={trackId}&quality={quality}"; var url = $"{baseUrl}/track/?id={trackId}&quality={quality}";
// Get download info from this endpoint // Get download info from this endpoint
var infoResponse = await _httpClient.GetAsync(url, ct); var infoResponse = await _httpClient.GetAsync(url, cancellationToken);
infoResponse.EnsureSuccessStatusCode(); infoResponse.EnsureSuccessStatusCode();
var json = await infoResponse.Content.ReadAsStringAsync(ct); var json = await infoResponse.Content.ReadAsStringAsync(cancellationToken);
var doc = JsonDocument.Parse(json); var doc = JsonDocument.Parse(json);
if (!doc.RootElement.TryGetProperty("data", out var data)) if (!doc.RootElement.TryGetProperty("data", out var data))
@@ -185,8 +185,8 @@ public class SquidWTFDownloadService : BaseDownloadService
request.Headers.Add("User-Agent", "Mozilla/5.0"); request.Headers.Add("User-Agent", "Mozilla/5.0");
request.Headers.Add("Accept", "*/*"); request.Headers.Add("Accept", "*/*");
return await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, ct); return await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
}, cancellationToken); });
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
@@ -228,8 +228,8 @@ public class SquidWTFDownloadService : BaseDownloadService
{ {
return await QueueRequestAsync(async () => return await QueueRequestAsync(async () =>
{ {
// Race all endpoints for fastest download info retrieval // Use round-robin with fallback instead of racing to reduce CPU usage
return await _fallbackHelper.RaceAllEndpointsAsync(async (baseUrl, ct) => return await _fallbackHelper.TryWithFallbackAsync(async (baseUrl) =>
{ {
// Map quality settings to Tidal's quality levels per hifi-api spec // Map quality settings to Tidal's quality levels per hifi-api spec
var quality = _squidwtfSettings.Quality?.ToUpperInvariant() switch var quality = _squidwtfSettings.Quality?.ToUpperInvariant() switch
@@ -246,10 +246,10 @@ public class SquidWTFDownloadService : BaseDownloadService
Logger.LogDebug("Fetching track download info from: {Url}", url); Logger.LogDebug("Fetching track download info from: {Url}", url);
var response = await _httpClient.GetAsync(url, ct); var response = await _httpClient.GetAsync(url, cancellationToken);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync(ct); var json = await response.Content.ReadAsStringAsync(cancellationToken);
var doc = JsonDocument.Parse(json); var doc = JsonDocument.Parse(json);
if (!doc.RootElement.TryGetProperty("data", out var data)) if (!doc.RootElement.TryGetProperty("data", out var data))
@@ -282,8 +282,7 @@ public class SquidWTFDownloadService : BaseDownloadService
? audioQualityEl.GetString() ? audioQualityEl.GetString()
: "LOSSLESS"; : "LOSSLESS";
Logger.LogDebug("Decoded manifest - URL: {Url}, MIME: {MimeType}, Quality: {Quality}", Logger.LogInformation("Track download URL obtained from hifi-api: {Url}", downloadUrl);
downloadUrl, mimeType, audioQuality);
return new DownloadResult return new DownloadResult
{ {
@@ -291,7 +290,7 @@ public class SquidWTFDownloadService : BaseDownloadService
MimeType = mimeType ?? "audio/flac", MimeType = mimeType ?? "audio/flac",
AudioQuality = audioQuality ?? "LOSSLESS" AudioQuality = audioQuality ?? "LOSSLESS"
}; };
}, cancellationToken); });
}); });
} }