using Microsoft.Extensions.Configuration; using MP.Core.Conf; using MP.Data.DbModels; using MP.Data.Repository.FluxLog; using Newtonsoft.Json; using NLog; using StackExchange.Redis; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Data; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using ZiggyCreatures.Caching.Fusion; namespace MP.Data.Services { /// /// Classe accesso processi traduzione /// public class TranslateSrv : BaseServ { #region Public Constructors public TranslateSrv( IConfiguration configuration, IConnectionMultiplexer redConn, IFusionCache cache, Repository.MpVoc.IMpVocRepository mpVocRepository ) : base(configuration, cache, redConn) { _mpVocRepository = mpVocRepository; } #endregion Public Constructors #region Public Methods /// /// Esegue traduzione dato vocabolario da Lingua + Lemma /// /// /// /// public string Traduci(string lemma, string lingua = "IT") { if (string.IsNullOrWhiteSpace(lemma)) return string.Empty; if (string.IsNullOrWhiteSpace(lingua)) return lemma; string linguaKey = lingua.ToLowerInvariant().Trim(); string cacheKey = $"vocab:{linguaKey}"; // FusionCache gestisce il lock e recupera l'intero dizionario della lingua. // Se è in L1 (Memory), restituisce l'oggetto C# istantaneamente. // Se non c'è, passa a L2 (Redis) o invoca la factory per caricarlo. var dizionarioLingua = _cache.GetOrSet>( cacheKey, _ => _mpVocRepository.VocabolarioGetLang(linguaKey), options => options .SetDuration(TimeSpan.FromHours(8)) // Durata logica della cache .SetFailSafe(true, TimeSpan.FromHours(1)) // Se Redis/DB è giù, usa i vecchi dati L1 ); // Ricerca O(1) nel dizionario in memoria if (dizionarioLingua != null && dizionarioLingua.TryGetValue(lemma, out var traduzione)) { return traduzione; } // Fallback: se la parola non è censita, restituisce il lemma originale (lingua + lemma) return $"{lingua}_{lemma}"; } #if false /// /// Recupero elenco config /// /// public async Task> ConfigGetAll() { string source = "DB"; Stopwatch sw = new Stopwatch(); sw.Start(); List? result = new List(); // cerco in _redisConn... string currKey = $"{redisBaseKey}:Conf"; RedisValue rawData = await _redisDb.StringGetAsync(currKey); //if (!string.IsNullOrEmpty($"{rawData}")) if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}"); source = "REDIS"; } else { result = await _mpVocRepository.ConfigGetAllAsync(); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); _redisDb.StringSet(currKey, rawData, LongCache); } if (result == null) { result = new List(); } sw.Stop(); Log.Debug($"ConfigGetAllAsync | {source} | {sw.Elapsed.TotalMilliseconds}ms"); return result; } #endif /// /// Pulizia cache Redis (tutta) /// /// public async Task FlushCache() { #if false RedisValue pattern = new RedisValue($"{redisBaseKey}:*"); bool answ = await ExecFlushRedisPattern(pattern); // rileggo vocabolario! var rawData = await VocabolarioGetAll(); DictVocab = rawData.ToDictionary(kvp => $"{kvp.Lingua}_{kvp.Lemma}".ToUpper(), kvp => kvp.Traduzione); return answ; #endif return await FlushFusionCacheAsync(); } /// /// Pulizia cache Redis per chiave specifica (da redisBaseKey...) /// /// public async Task FlushCache(string KeyReq) { #if false RedisValue pattern = new RedisValue($"{redisBaseKey}:{KeyReq}:*"); bool answ = await ExecFlushRedisPattern(pattern); return answ; #endif return await FlushFusionCacheAsync(KeyReq); } /// /// Cancellazione FusionCache (totale) /// /// private async Task FlushFusionCacheAsync() { await _cache.ClearAsync(allowFailSafe: false); return true; } /// /// Cancellazione FusionCache dato singolo tag /// /// private async Task FlushFusionCacheAsync(string tag) { if (string.IsNullOrWhiteSpace(tag)) return false; await _cache.RemoveByTagAsync(tag); return true; } /// /// Cancellazione FusionCache dato elenco tags /// /// private async Task FlushFusionCacheAsync(List listTags) { if (listTags == null || listTags.Count == 0) return false; // Generiamo i Task di rimozione ed eseguiamoli in parallelo su Redis/L1 var tasks = listTags .Where(tag => !string.IsNullOrWhiteSpace(tag)) .Select(tag => _cache.RemoveByTagAsync(tag).AsTask()); await Task.WhenAll(tasks); return true; } /// /// Recupero elenco config /// /// public async Task> LingueGetAll() { string currKey = $"{redisBaseKey}:Lang"; return await GetOrFetchAsync( operationName: "LingueGetAll", cacheKey: currKey, expiration: GetRandTOut(redisShortTimeCache), fetchFunc: async () => await _mpVocRepository.LingueGetAllAsync() ?? new(), tagList: [currKey] ); #if false string source = "DB"; Stopwatch sw = new Stopwatch(); sw.Start(); List? result = new List(); // cerco in _redisConn... string currKey = $"{redisBaseKey}:Lang"; RedisValue rawData = await _redisDb.StringGetAsync(currKey); //if (!string.IsNullOrEmpty($"{rawData}")) if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}"); source = "REDIS"; } else { result = await _mpVocRepository.LingueGetAllAsync(); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); _redisDb.StringSet(currKey, rawData, UltraLongCache); } if (result == null) { result = new List(); } sw.Stop(); Log.Debug($"LingueGetAll | {source} | {sw.Elapsed.TotalMilliseconds}ms"); return result; #endif } #if false /// /// Traduzione termine /// /// /// /// public string Traduci(string lemma, string lingua = "IT") { string answ = $"[{lemma}.{lingua}]"; string key = $"{lingua}_{lemma}".ToUpper(); if (DictVocab.ContainsKey(key)) { answ = DictVocab[key]; } return answ; } #endif /// /// Recupero elenco config /// /// public async Task> VocabolarioGetAll() { string currKey = $"{redisBaseKey}:VocAll"; return await GetOrFetchAsync( operationName: "VocabolarioGetAll", cacheKey: currKey, expiration: GetRandTOut(redisShortTimeCache), fetchFunc: async () => await _mpVocRepository.VocabolarioGetAllAsync() ?? new(), tagList: [currKey] ); #if false string source = "DB"; Stopwatch sw = new Stopwatch(); sw.Start(); List? result = new List(); // cerco in _redisConn... string currKey = $"{redisBaseKey}:Voc"; RedisValue rawData = await _redisDb.StringGetAsync(currKey); //if (!string.IsNullOrEmpty($"{rawData}")) if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}"); source = "REDIS"; } else { result = await _mpVocRepository.VocabolarioGetAllAsync(); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); _redisDb.StringSet(currKey, rawData, UltraLongCache); } if (result == null) { result = new List(); } sw.Stop(); Log.Debug($"VocabolarioGetAll | {source} | {sw.Elapsed.TotalMilliseconds}ms"); return result; #endif } #endregion Public Methods #region Protected Fields #if false /// /// Vocabolario x recupero rapido traduzioni /// protected static Dictionary DictVocab = new Dictionary(); #endif #endregion Protected Fields #region Protected Methods #endregion Protected Methods #region Private Fields private static Logger Log = LogManager.GetCurrentClassLogger(); private string redisBaseKey = "MP:Voc:Cache"; private readonly Repository.MpVoc.IMpVocRepository _mpVocRepository; #endregion Private Fields #region Private Methods #if false /// /// Esegue flush memoria _redisConn dato pat2Flush /// /// /// private async Task ExecFlushRedisPattern(RedisValue pat2Flush) { bool answ = false; var masterEndpoint = _redisConn.GetEndPoints() .Where(ep => _redisConn.GetServer(ep).IsConnected && !_redisConn.GetServer(ep).IsReplica) .FirstOrDefault(); // sepattern è "*" elimino intero DB... if (masterEndpoint != null && (pat2Flush.Equals(new RedisValue("*")) || pat2Flush == RedisValue.Null)) { _redisConn.GetServer(masterEndpoint).FlushDatabase(database: _redisDb.Database); } else { var server = _redisConn.GetServer(masterEndpoint); var keys = server.Keys(database: _redisDb.Database, pattern: pat2Flush, pageSize: 1000); var deleteTasks = new List(); foreach (var key in keys) { deleteTasks.Add(_redisDb.KeyDeleteAsync(key)); if (deleteTasks.Count >= 1000) { await Task.WhenAll(deleteTasks); deleteTasks.Clear(); } } if (deleteTasks.Count > 0) { await Task.WhenAll(deleteTasks); } } answ = true; return answ; } #endif #endregion Private Methods } }