using Microsoft.Extensions.Options; using allstarr.Models.Settings; using StackExchange.Redis; using System.Text.Json; namespace allstarr.Services.Common; /// /// Redis caching service for metadata and images. /// public class RedisCacheService { private readonly RedisSettings _settings; private readonly ILogger _logger; private IConnectionMultiplexer? _redis; private IDatabase? _db; private readonly object _lock = new(); public RedisCacheService( IOptions settings, ILogger 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; /// /// Gets a cached value as a string. /// public async Task GetStringAsync(string key) { if (!IsEnabled) return null; try { var value = await _db!.StringGetAsync(key); if (value.HasValue) { _logger.LogInformation("Redis cache HIT: {Key}", key); } else { _logger.LogInformation("Redis cache MISS: {Key}", key); } return value; } catch (Exception ex) { _logger.LogWarning(ex, "Redis GET failed for key: {Key}", key); return null; } } /// /// Gets a cached value and deserializes it. /// public async Task GetAsync(string key) where T : class { var json = await GetStringAsync(key); if (string.IsNullOrEmpty(json)) return null; try { return JsonSerializer.Deserialize(json); } catch (Exception ex) { _logger.LogWarning(ex, "Failed to deserialize cached value for key: {Key}", key); return null; } } /// /// Sets a cached value with TTL. /// public async Task SetStringAsync(string key, string value, TimeSpan? expiry = null) { if (!IsEnabled) return false; try { var result = await _db!.StringSetAsync(key, value, expiry); if (result) { _logger.LogInformation("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; } } /// /// Sets a cached value by serializing it with TTL. /// public async Task SetAsync(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; } } /// /// Deletes a cached value. /// public async Task 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; } } /// /// Checks if a key exists. /// public async Task 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; } } }