Files
allstarr/allstarr/Services/SquidWTF/SquidWTFStartupValidator.cs

142 lines
5.0 KiB
C#

using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Options;
using allstarr.Models.Settings;
using allstarr.Services.Validation;
namespace allstarr.Services.SquidWTF;
/// <summary>
/// Validates SquidWTF service connectivity at startup (no auth needed)
/// </summary>
public class SquidWTFStartupValidator : BaseStartupValidator
{
private readonly SquidWTFSettings _settings;
private readonly List<string> _apiUrls;
private int _currentUrlIndex = 0;
private readonly object _urlIndexLock = new object();
public override string ServiceName => "SquidWTF";
public SquidWTFStartupValidator(IOptions<SquidWTFSettings> settings, HttpClient httpClient, List<string> apiUrls)
: base(httpClient)
{
_settings = settings.Value;
_apiUrls = apiUrls;
}
/// <summary>
/// Tries the request with the next provider in round-robin, then falls back to others on failure.
/// This distributes load evenly across all providers while maintaining reliability.
/// </summary>
private async Task<T> TryWithFallbackAsync<T>(Func<string, Task<T>> action, T defaultValue)
{
// Start with the next URL in round-robin to distribute load
var startIndex = 0;
lock (_urlIndexLock)
{
startIndex = _currentUrlIndex;
_currentUrlIndex = (_currentUrlIndex + 1) % _apiUrls.Count;
}
// Try all URLs starting from the round-robin selected one
for (int attempt = 0; attempt < _apiUrls.Count; attempt++)
{
var urlIndex = (startIndex + attempt) % _apiUrls.Count;
var baseUrl = _apiUrls[urlIndex];
try
{
return await action(baseUrl);
}
catch
{
WriteDetail($"Endpoint {baseUrl} failed, trying next...");
if (attempt == _apiUrls.Count - 1)
{
WriteDetail($"All {_apiUrls.Count} endpoints failed");
return defaultValue;
}
}
}
return defaultValue;
}
public override async Task<ValidationResult> ValidateAsync(CancellationToken cancellationToken)
{
Console.WriteLine();
var quality = _settings.Quality?.ToUpperInvariant() switch
{
"FLAC" => "LOSSLESS",
"HI_RES" => "HI_RES_LOSSLESS",
"LOSSLESS" => "LOSSLESS",
"HIGH" => "HIGH",
"LOW" => "LOW",
_ => "LOSSLESS (default)"
};
WriteStatus("SquidWTF Quality", quality, ConsoleColor.Cyan);
// Test connectivity with fallback
var result = await TryWithFallbackAsync(async (baseUrl) =>
{
var response = await _httpClient.GetAsync(baseUrl, cancellationToken);
if (response.IsSuccessStatusCode)
{
WriteStatus("SquidWTF API", $"REACHABLE ({baseUrl})", ConsoleColor.Green);
WriteDetail("No authentication required - powered by Tidal");
// Try a test search to verify functionality
await ValidateSearchFunctionality(baseUrl, cancellationToken);
return ValidationResult.Success("SquidWTF validation completed");
}
else
{
throw new HttpRequestException($"HTTP {(int)response.StatusCode}");
}
}, ValidationResult.Failure("-1", "All SquidWTF endpoints failed"));
return result;
}
private async Task ValidateSearchFunctionality(string baseUrl, CancellationToken cancellationToken)
{
try
{
// Test search with a simple query
var searchUrl = $"{baseUrl}/search/?s=Taylor%20Swift";
var searchResponse = await _httpClient.GetAsync(searchUrl, cancellationToken);
if (searchResponse.IsSuccessStatusCode)
{
var json = await searchResponse.Content.ReadAsStringAsync(cancellationToken);
var doc = JsonDocument.Parse(json);
if (doc.RootElement.TryGetProperty("data", out var data) &&
data.TryGetProperty("items", out var items))
{
var itemCount = items.GetArrayLength();
WriteStatus("Search Functionality", "WORKING", ConsoleColor.Green);
WriteDetail($"Test search returned {itemCount} results");
}
else
{
WriteStatus("Search Functionality", "UNEXPECTED RESPONSE", ConsoleColor.Yellow);
}
}
else
{
WriteStatus("Search Functionality", $"HTTP {(int)searchResponse.StatusCode}", ConsoleColor.Yellow);
}
}
catch (Exception ex)
{
WriteStatus("Search Functionality", "ERROR", ConsoleColor.Yellow);
WriteDetail($"Could not verify search: {ex.Message}");
}
}
}