mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-10 16:08:39 -05:00
Major Features: - Spotify playlist injection with missing tracks search - Transparent proxy authentication system - WebSocket session management for external tracks - Manual track mapping and favorites system - Lyrics support (Spotify + LRCLib) with prefetching - Admin dashboard with analytics and configuration - Performance optimizations with health checks and endpoint racing - Comprehensive caching and memory management Performance Improvements: - Quick health checks (3s timeout) before trying endpoints - Health check results cached for 30 seconds - 5 minute timeout for large artist responses - Background Odesli conversion after streaming starts - Parallel lyrics prefetching - Endpoint benchmarking and racing - 16 SquidWTF endpoints with load balancing Reliability: - Automatic endpoint fallback and failover - Token expiration handling - Concurrent request optimization - Memory leak fixes - Proper session cleanup User Experience: - Web UI for configuration and playlist management - Real-time progress tracking - API analytics dashboard - Manual track mapping interface - Playlist statistics and health monitoring
203 lines
5.4 KiB
C#
203 lines
5.4 KiB
C#
using Microsoft.Extensions.Options;
|
|
using allstarr.Models.Settings;
|
|
using StackExchange.Redis;
|
|
using System.Text.Json;
|
|
|
|
namespace allstarr.Services.Common;
|
|
|
|
/// <summary>
|
|
/// Redis caching service for metadata and images.
|
|
/// </summary>
|
|
public class RedisCacheService
|
|
{
|
|
private readonly RedisSettings _settings;
|
|
private readonly ILogger<RedisCacheService> _logger;
|
|
private IConnectionMultiplexer? _redis;
|
|
private IDatabase? _db;
|
|
private readonly object _lock = new();
|
|
|
|
public RedisCacheService(
|
|
IOptions<RedisSettings> settings,
|
|
ILogger<RedisCacheService> logger)
|
|
{
|
|
_settings = settings.Value;
|
|
_logger = logger;
|
|
|
|
if (_settings.Enabled)
|
|
{
|
|
InitializeConnection();
|
|
}
|
|
}
|
|
|
|
private void InitializeConnection()
|
|
{
|
|
try
|
|
{
|
|
_redis = ConnectionMultiplexer.Connect(_settings.ConnectionString);
|
|
_db = _redis.GetDatabase();
|
|
_logger.LogInformation("Redis connected: {ConnectionString}", _settings.ConnectionString);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Redis connection failed. Caching disabled.");
|
|
_redis = null;
|
|
_db = null;
|
|
}
|
|
}
|
|
|
|
public bool IsEnabled => _settings.Enabled && _db != null;
|
|
|
|
/// <summary>
|
|
/// Gets a cached value as a string.
|
|
/// </summary>
|
|
public async Task<string?> GetStringAsync(string key)
|
|
{
|
|
if (!IsEnabled) return null;
|
|
|
|
try
|
|
{
|
|
var value = await _db!.StringGetAsync(key);
|
|
|
|
if (value.HasValue)
|
|
{
|
|
_logger.LogDebug("Redis cache HIT: {Key}", key);
|
|
}
|
|
else
|
|
{
|
|
_logger.LogDebug("Redis cache MISS: {Key}", key);
|
|
}
|
|
return value;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Redis GET failed for key: {Key}", key);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a cached value and deserializes it.
|
|
/// </summary>
|
|
public async Task<T?> GetAsync<T>(string key) where T : class
|
|
{
|
|
var json = await GetStringAsync(key);
|
|
if (string.IsNullOrEmpty(json)) return null;
|
|
|
|
try
|
|
{
|
|
return JsonSerializer.Deserialize<T>(json);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to deserialize cached value for key: {Key}", key);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets a cached value with TTL.
|
|
/// </summary>
|
|
public async Task<bool> SetStringAsync(string key, string value, TimeSpan? expiry = null)
|
|
{
|
|
if (!IsEnabled) return false;
|
|
|
|
try
|
|
{
|
|
var result = await _db!.StringSetAsync(key, value, expiry);
|
|
if (result)
|
|
{
|
|
_logger.LogDebug("Redis cache SET: {Key} (TTL: {Expiry})", key, expiry?.ToString() ?? "none");
|
|
}
|
|
return result;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Redis SET failed for key: {Key}", key);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets a cached value by serializing it with TTL.
|
|
/// </summary>
|
|
public async Task<bool> SetAsync<T>(string key, T value, TimeSpan? expiry = null) where T : class
|
|
{
|
|
try
|
|
{
|
|
var json = JsonSerializer.Serialize(value);
|
|
return await SetStringAsync(key, json, expiry);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to serialize value for key: {Key}", key);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes a cached value.
|
|
/// </summary>
|
|
public async Task<bool> DeleteAsync(string key)
|
|
{
|
|
if (!IsEnabled) return false;
|
|
|
|
try
|
|
{
|
|
return await _db!.KeyDeleteAsync(key);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Redis DELETE failed for key: {Key}", key);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if a key exists.
|
|
/// </summary>
|
|
public async Task<bool> ExistsAsync(string key)
|
|
{
|
|
if (!IsEnabled) return false;
|
|
|
|
try
|
|
{
|
|
return await _db!.KeyExistsAsync(key);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Redis EXISTS failed for key: {Key}", key);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes all keys matching a pattern (e.g., "search:*").
|
|
/// WARNING: Use with caution as this scans all keys.
|
|
/// </summary>
|
|
public async Task<int> DeleteByPatternAsync(string pattern)
|
|
{
|
|
if (!IsEnabled) return 0;
|
|
|
|
try
|
|
{
|
|
var server = _redis!.GetServer(_redis.GetEndPoints().First());
|
|
var keys = server.Keys(pattern: pattern).ToArray();
|
|
|
|
if (keys.Length == 0)
|
|
{
|
|
_logger.LogDebug("No keys found matching pattern: {Pattern}", pattern);
|
|
return 0;
|
|
}
|
|
|
|
var deleted = await _db!.KeyDeleteAsync(keys);
|
|
_logger.LogInformation("Deleted {Count} Redis keys matching pattern: {Pattern}", deleted, pattern);
|
|
return (int)deleted;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Redis DELETE BY PATTERN failed for pattern: {Pattern}", pattern);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|