using EgwMultiEngineManager.Data; using Microsoft.Extensions.Configuration; using Newtonsoft.Json; using NLog; using RestSharp; using StackExchange.Redis; namespace EgwCoreLib.Lux.Data.Services { public class ImageCacheService { #region Public Constructors public ImageCacheService(IConfiguration config, IRedisService redisService, IConnectionMultiplexer redisConn) { _config = config; _redisService = redisService; _redisConn = redisConn; _redisDb = redisConn.GetDatabase(); // conf channels x comunicazione broadcast chBom = _config.GetValue("ServerConf:ChannelBom") ?? "lux:bom"; chHwList = _config.GetValue("ServerConf:ChannelHwList") ?? "lux:hw:list"; chHwOpt = _config.GetValue("ServerConf:ChannelHwOpt") ?? "lux:hw:opt"; chPng = _config.GetValue("ServerConf:ChannelPng") ?? "lux:png:img"; chProfList = _config.GetValue("ServerConf:ChannelProfList") ?? "lux:prof"; chShape = _config.GetValue("ServerConf:ChannelShape") ?? "lux:shape"; chSvg = _config.GetValue("ServerConf:ChannelSvg") ?? "lux:svg:img"; chUpdate = _config.GetValue("ServerConf:ChannelUpdate") ?? "lux:update"; // conf tag x cache liveTag = _config.GetValue("ServerConf:ImageLiveTag") ?? "svg"; cacheTag = _config.GetValue("ServerConf:ImageFileTag") ?? "svgfile"; calcTag = _config.GetValue("ServerConf:ImageCalcTag") ?? "svg-preview"; // verifico la url base apiUrl = _config.GetValue("ServerConf:Prog.ApiUrl") ?? "https://iis01.egalware.com/lux/srv/api"; imgBasePath = _config.GetValue("ServerConf:ImageBaseUrl") ?? "window"; // fix opzioni base del RestClient restOptStd = new RestClientOptions { Timeout = TimeSpan.FromSeconds(60), BaseUrl = new Uri($"{apiUrl}/{imgBasePath}") }; Log.Info("ImageCache Service Started"); } #endregion Public Constructors #region Public Methods /// /// Effettua una generica chiamata ApiRest /// /// URL Api di base /// Richiesta completa /// public async Task CallRestGet(string ApiUrl, string ApiRequest) { RestResponse response; // cerco online using (RestClient client = new RestClient(ApiUrl)) { var request = new RestRequest(ApiRequest, Method.Get); response = await client.GetAsync(request); } return response; } /// /// Effettua una generica chiamata ApiRest /// /// URL Api di base /// Richiesta completa /// Corpo Json trasmesso con richiesta /// public async Task CallRestPost(string ApiUrl, string ApiRequest, object rawBody) { RestResponse response; // cerco online using (RestClient client = new RestClient(ApiUrl)) { var request = new RestRequest(ApiRequest, Method.Post); string rawData = JsonConvert.SerializeObject(rawBody); request.AddJsonBody(rawData); response = await client.ExecutePostAsync(request); } return response; } /// /// Esegue flush memoria redis dato pat2Flush /// /// /// public async Task ExecFlushRedisPatternAsync(RedisValue pat2Flush) { bool answ = false; /******************************* * Recupero elenco dei server da cui cancellare le chaivi: * - prendo solo i server connessi * - prendo solo NON repliche (= master) * - me ne aspetto 1 in uscita / prendo primo o default *******************************/ var connServ = _redisConn.GetEndPoints() .Select(endpoint => { var server = _redisConn.GetServer(endpoint); return server; }) .Where(x => x.IsConnected && !x.IsReplica) .FirstOrDefault(); if (connServ != null) { // ciclo (anche se me ne aspetto 1 solo) // se pat2Flush è "*" elimino intero DB... if ((pat2Flush.Equals(new RedisValue("*")) || pat2Flush == RedisValue.Null)) { connServ.FlushDatabase(database: _redisDb.Database); } else { try { var keys = connServ.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; } catch (Exception exc) { Log.Error($"Eccezione durante ExecFlushRedisPatternAsync | pat2Flush: {pat2Flush}{Environment.NewLine}{exc}"); } } } else { Log.Error($"Server REDIS master non trovato"); } return answ; } /// /// Calcola URL immagine dato /// /// Base url IMG server (da config) /// Live = in fase di calcolo (cache REDIS), altrimenti da filesystem /// UID elemento/immagine /// Environment item /// public string ImageUrl(string baseUrl, bool isLive, string imgUID, EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS envir = EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS.WINDOW) { string tag = isLive ? liveTag : cacheTag; string fType = "undef"; if (imgUID.EndsWith(".svg")) { fType = "svg"; imgUID.Replace(".svg", ""); } else if (imgUID.EndsWith(".png")) { fType = "png"; imgUID.Replace(".png", ""); } switch (envir) { case Constants.EXECENVIRONMENTS.NULL: break; case Constants.EXECENVIRONMENTS.WINDOW: fType = "svg"; break; case Constants.EXECENVIRONMENTS.BEAM: case Constants.EXECENVIRONMENTS.WALL: case Constants.EXECENVIRONMENTS.CABINET: default: fType = "png"; break; } string rndImg = $"{DateTime.Now:HHmmssfff}"; string fullUrl = $"{baseUrl}/{tag}/{imgUID}-{rndImg}.{fType}?env={envir}".Replace("////", "//"); return fullUrl; } /// /// Rilettura risposta da debug in formato string x riprocessare... /// /// /// /// public async Task LoadDebug(string uID, Constants.EXECENVIRONMENTS env) { // salvo img in cache string currKey = $"{redisBaseKey}:DEBUG:{env}:{uID}"; string answ = await _redisService.GetAsync(currKey) ?? ""; return answ; } /// /// Recupera img redis PNG x id item richiesto /// /// UID item /// Environment item public string LoadPng(string imgUid, EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS envir = EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS.WINDOW) { // recupero img da cache string currKey = $"{redisBaseKey}:{envir}:Img:Png:{imgUid.Replace("/", ":")}"; var rawVal = _redisService.Get(currKey); return $"{rawVal}"; } /// /// Recupera img redis SVG x id item richiesto /// /// UID item /// Environment item public string LoadSvg(string imgUid, EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS envir = EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS.WINDOW) { // recupero img da cache string currKey = $"{redisBaseKey}:{envir}:Img:Svg:{imgUid.Replace("/", ":")}"; var rawVal = _redisService.Get(currKey); return $"{rawVal}"; } /// /// Recupera img redis SVG x id item richiesto async /// /// UID item /// Environment item public async Task LoadSvgAsync(string imgId, EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS envir = EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS.WINDOW) { // recupero img da cache string currKey = $"{redisBaseKey}:{envir}:Img:Svg:{imgId.Replace("/", ":")}"; var rawVal = await _redisService.GetAsync(currKey); return $"{rawVal}"; } /// /// Salva e invia su channel richiesto un update x UID /// è attesao un refresh per chi sottoscrive il canale /// /// /// public async Task PublishUpdateAsync(string uid, Constants.EXECENVIRONMENTS env) { bool done = false; // invio notifica sul canale di update generale... string notifyChannel = $"{chUpdate}:{env}"; // contenuto è UID long numSent = await _redisService.PublishAsync(notifyChannel, $"{uid}"); done = numSent > 0; return done; } /// /// Calcola URL immagine dato /// /// Base url IMG server (da config) /// UID elemento/immagine /// JWD serializzato di cui si richiede l'immagine aggiornata /// public string ReqUpdateSvg(string baseUrl, string imgUID, string serJwd) { string answ = ""; if (imgUID.EndsWith(".svg")) { imgUID.Replace(".svg", ""); } string fullUrl = $"{baseUrl}/svg-preview/{imgUID}".Replace("////", "//"); return answ; } /// /// Salva e invia su channel BOM redis il doc richiesto /// /// /// /// public async Task SaveBomAsync(string uid, Constants.EXECENVIRONMENTS env, string bomContent) { // salvo in cache string currKey = $"{redisBaseKey}:{env}:BOM:{uid.Replace("/", ":")}"; var done = await _redisService.SetAsync(currKey, bomContent); // invio notifica nuova svg generata sul canale... viene inviato solo svg con ID nel canale string notifyChannel = $"{chBom}:{uid}"; long numSent = await _redisService.PublishAsync(notifyChannel, bomContent); return done; } /// /// Salvataggio risposta x debug in formato RAW... /// /// /// /// /// /// public async Task SaveDebug(string uID, Constants.EXECENVIRONMENTS env, string rawData) { // salvo img in cache string currKey = $"{redisBaseKey}:DEBUG:{env}:{uID}"; var done = await _redisService.SetAsync(currKey, rawData); return done; } /// /// Salva e invia su channel risultato stima per POR (UID.GroupId) richiesto /// /// /// /// public async Task SaveProdEstimationAsync(string uid, EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS env, string estimContent) { // salvo in cache string currKey = $"{redisBaseKey}:{env}:Estimation:{uid}"; var done = await _redisService.SetAsync(currKey, estimContent); // invio notifica nuova stima sul canale... viene inviato SOLO che è stata fatta (DONE) x UID (POR)... string notifyChannel = $"{chEstimation}:{uid}"; long numSent = await _redisService.PublishAsync(notifyChannel, "DONE"); //long numSent = await _redisService.PublishAsync(notifyChannel, estimContent); return done; } /// /// Salva e invia su channel risultato balance per POR (UID.GroupId.Type) richiesto /// /// /// /// public async Task SaveProdBalanceAsync(string uid, EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS env, string estimContent) { // salvo in cache string currKey = $"{redisBaseKey}:{env}:Balance:{uid}"; var done = await _redisService.SetAsync(currKey, estimContent); // invio notifica nuova stima sul canale... viene inviato SOLO che è stata fatta (DONE) x UID (POR)... string notifyChannel = $"{chBalance}:{uid}"; long numSent = await _redisService.PublishAsync(notifyChannel, "DONE"); //long numSent = await _redisService.PublishAsync(notifyChannel, estimContent); return done; } /// /// Salva e invia su channel dedicato HML l'elenco degli HarwareModel (conf preferiti) gestiti /// /// /// public async Task SaveHmlAsync(string uid, Constants.EXECENVIRONMENTS env, string rawData) { // salvo img in cache string currKey = $"{redisBaseKey}:{env}:HML:{uid.Replace("/", ":")}"; var done = await _redisService.SetAsync(currKey, rawData); // invio notifica nuova svg generata sul canale... viene inviato solo svg con ID nel canale string notifyChannel = $"{chHwList}:{uid}"; long numSent = await _redisService.PublishAsync(notifyChannel, rawData); return done; } /// /// Salva e invia su channel l'XML delle HW options per il doc richiesto /// /// /// /// public async Task SaveHwOptAsync(string uid, EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS env, string xmlContent) { // salvo img in cache string currKey = $"{redisBaseKey}:{env}:HwOpt:{uid.Replace("/", ":")}"; // lascio in cache 15 min x poterlo verificare var done = await _redisService.SetAsync(currKey, xmlContent, TimeSpan.FromMinutes(15)); // invio notifica nuova XML sul canale (inviato solo XML pulito con ID nel canale) string notifyChannel = $"{chHwOpt}:{uid}"; long numSent = await _redisService.PublishAsync(notifyChannel, xmlContent); return done; } /// /// Salva e invia su channel img redis SVG per il doc richiesto /// /// /// contenuto PNG come string base64 /// public bool SavePng(string imgId, EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS env, string pngContent) { // salvo img in cache string currKey = $"{redisBaseKey}:{env}:Img:Png:{imgId.Replace("/", ":")}"; var done = _redisService.Set(currKey, pngContent); // invio notifica nuova imgsvg sul canale... viene "reimbustata" string notifyChannel = $"png-gen:{imgId}"; long numSent = _redisService.Publish(notifyChannel, pngContent); return done; } /// /// Salva e invia su channel img redis PNG per il doc richiesto /// /// /// /// contenuto PNG come string base64 public async Task SavePngAsync(string imgId, EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS env, string pngContent) { // salvo img in cache string currKey = $"{redisBaseKey}:{env}:Img:Png:{imgId.Replace("/", ":")}"; var done = await _redisService.SetAsync(currKey, pngContent); // invio notifica nuova png generata sul canale... viene inviato solo contenuto con ID nel canale string notifyChannel = $"{chPng}:{imgId}"; long numSent = await _redisService.PublishAsync(notifyChannel, "PNG"); // NON pubblico contenuto che x PNG non serve... //long numSent = await _redisService.PublishAsync(notifyChannel, pngContent); return done; } /// /// Salva e invia su channel profile list gestiti /// /// /// public async Task SaveProfileListAsync(string uid, Constants.EXECENVIRONMENTS env, string rawData) { // salvo img in cache string currKey = $"{redisBaseKey}:{env}:PROF:LIST:{uid.Replace("/", ":")}"; var done = await _redisService.SetAsync(currKey, rawData); // invio notifica nuova svg generata sul canale... viene inviato solo svg con ID nel canale string notifyChannel = $"{chProfList}:{uid}"; long numSent = await _redisService.PublishAsync(notifyChannel, rawData); return done; } /// /// Salva e invia su channel profile threshold x profilo gestiti /// /// /// public async Task SaveProfileThreshAsync(string uid, Constants.EXECENVIRONMENTS env, string rawData) { // salvo img in cache string currKey = $"{redisBaseKey}:{env}:PROF:THRESH:{uid.Replace("/", ":")}"; var done = await _redisService.SetAsync(currKey, rawData); // invio notifica nuova svg generata sul canale... viene inviato solo svg con ID nel canale string notifyChannel = $"{chProfThresh}:{uid}"; long numSent = await _redisService.PublishAsync(notifyChannel, rawData); return done; } /// /// Salva e invia su channel shape per item (UID.GroupId) richiesto /// /// /// /// public async Task SaveShapeAsync(string uid, EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS env, string shapeContent) { // salvo img in cache string currKey = $"{redisBaseKey}:{env}:Shape:{uid}"; var done = await _redisService.SetAsync(currKey, shapeContent); // invio notifica nuova svg generata sul canale... viene inviato solo svg con ID nel canale string notifyChannel = $"{chShape}:{uid}"; long numSent = await _redisService.PublishAsync(notifyChannel, shapeContent); return done; } /// /// Salva e invia su channel img redis SVG per il doc richiesto /// /// /// /// public bool SaveSvg(string imgId, EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS env, string svgContent) { // salvo img in cache string currKey = $"{redisBaseKey}:{env}:Img:Svg:{imgId.Replace("/", ":")}"; var done = _redisService.Set(currKey, svgContent); // invio notifica nuova imgsvg sul canale... viene "reimbustata" string notifyChannel = $"svg-gen:{imgId}"; long numSent = _redisService.Publish(notifyChannel, svgContent); return done; } /// /// Salva e invia su channel img redis SVG per il doc richiesto /// /// /// /// public async Task SaveSvgAsync(string imgId, EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS env, string svgContent) { // salvo img in cache string currKey = $"{redisBaseKey}:{env}:Img:Svg:{imgId.Replace("/", ":")}"; var done = await _redisService.SetAsync(currKey, svgContent); // invio notifica nuova svg generata sul canale... viene inviato solo contenuto con ID nel canale string notifyChannel = $"{chSvg}:{imgId}"; long numSent = await _redisService.PublishAsync(notifyChannel, svgContent); return done; } #endregion Public Methods #region Protected Fields /// /// Oggetto per la connessione a Redis utilizzato per operazioni di lettura/scrittura. /// protected IConnectionMultiplexer _redisConn = null!; /// /// Database Redis utilizzato per le operazioni di lettura/scrittura /// nb: ottenuto tramite _redisConn.GetDatabase() /// protected IDatabase _redisDb = null!; #endregion Protected Fields /// #if false /// Effettua una generica chiamata ApiRest /// /// URL Api di base /// Richiesta completa /// Corpo Json trasmesso con richiesta /// public async Task CallRestPost(string ApiUrl, string ApiRequest, string JsonBody) { RestResponse response; // cerco online using (RestClient client = new RestClient(ApiUrl)) { var request = new RestRequest(ApiRequest, Method.Post); string rawData = JsonConvert.SerializeObject(JsonBody); request.AddJsonBody(rawData); response = await client.ExecutePostAsync(request); } return response; } #endif #region Private Fields private static Logger Log = LogManager.GetCurrentClassLogger(); private readonly IRedisService _redisService; private readonly string apiUrl = ""; private readonly string cacheTag = "svgfile"; private readonly string calcTag = "svgpreview"; private readonly string chBalance = "balance:curr"; private readonly string chBom = "lux:bom"; private readonly string chEstimation = "estimation:curr"; private readonly string chHwList = "lux:hw:list"; private readonly string chHwOpt = "lux:hw:opt"; private readonly string chPng = "lux:png:img"; private readonly string chProfList = "lux:prof:list"; private readonly string chProfThresh = "lux:prof:thresh"; private readonly string chShape = "shape:curr"; private readonly string chSvg = "lux:svg:img"; private readonly string chUpdate = "lux::update"; private readonly string imgBasePath = ""; private readonly string liveTag = "svg"; private IConfiguration _config; private string imageBaseUrl = ""; private string redisBaseKey = "Lux"; #endregion Private Fields #region Private Properties /// /// Conf client RestSharp standard: /// - base URI al sito /// - timeout 1 min /// private RestClientOptions restOptStd { get; set; } = new RestClientOptions(); #endregion Private Properties } }