using MP.Data; using MP.Data.Conf; using MP.Data.DatabaseModels; using Newtonsoft.Json; using NLog; using StackExchange.Redis; using System.Diagnostics; using System.Text; namespace MP.SPEC.Data { public class MpDataService : IDisposable { #region Public Constructors public MpDataService(IConfiguration configuration, ILogger logger) { _logger = logger; _configuration = configuration; // setup compoenti REDIS redisConn = ConnectionMultiplexer.Connect(_configuration.GetConnectionString("Redis")); redisDb = redisConn.GetDatabase(); // setup canali pub/sub dataPipe = new MessagePipe(redisConn, Constants.ACT_MSE_DATA_KEY); blinkPipe = new MessagePipe(redisConn, Constants.ACT_BLINK_KEY); // conf DB string connStr = _configuration.GetConnectionString("Mp.Data"); if (string.IsNullOrEmpty(connStr)) { _logger.LogError("ConnString empty!"); } else { dbController = new MP.Data.Controllers.MpSpecController(configuration); StringBuilder sb = new StringBuilder(); sb.AppendLine($"DbController OK"); _logger.LogInformation(sb.ToString()); } // setup conf IOB da dizionario tryLoadIobTags(); #if false // avvio timers... startTimers(); #endif } #endregion Public Constructors #region Public Properties public static MP.Data.Controllers.MpSpecController dbController { get; set; } = null!; public MessagePipe blinkPipe { get; set; } = null!; /// /// Dizionario dei tag configurati per IOB /// public Dictionary> currTagConf { get; set; } = new Dictionary>(); public MessagePipe dataPipe { get; set; } = null!; #endregion Public Properties #region Public Methods public async Task> AnagStatiComm() { int maxAgeConfig = 5; Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); List? result = new List(); // cerco in redis... RedisValue rawData = await redisDb.StringGetAsync(redisStatoCom); if (!string.IsNullOrEmpty($"{rawData}")) { result = JsonConvert.DeserializeObject>($"{rawData}"); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"AnagStatiComm Read from REDIS: {ts.TotalMilliseconds}ms"); } else { result = await Task.FromResult(dbController.AnagStatiComm()); // serializzp e salvo... rawData = JsonConvert.SerializeObject(result); await redisDb.StringSetAsync(redisStatoCom, rawData, TimeSpan.FromMinutes(maxAgeConfig)); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"AnagStatiComm Read from DB: {ts.TotalMilliseconds}ms"); } if (result == null) { result = new List(); } return result; } public async Task> AnagTipoArtLV() { int maxAgeConfig = 5; Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); List? result = new List(); // cerco in redis... RedisValue rawData = await redisDb.StringGetAsync(redisTipoArt); if (!string.IsNullOrEmpty($"{rawData}")) { result = JsonConvert.DeserializeObject>($"{rawData}"); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"AnagTipoArtLV Read from REDIS: {ts.TotalMilliseconds}ms"); } else { result = await Task.FromResult(dbController.AnagTipoArtLV()); // serializzp e salvo... rawData = JsonConvert.SerializeObject(result); await redisDb.StringSetAsync(redisTipoArt, rawData, TimeSpan.FromMinutes(maxAgeConfig)); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"AnagTipoArtLV Read from DB: {ts.TotalMilliseconds}ms"); } if (result == null) { result = new List(); } return result; } /// /// Eliminazione record selezionato /// /// /// public async Task ArticoliDeleteRecord(AnagArticoli currRec) { return await dbController.ArticoliDeleteRecord(currRec); } /// /// Restitusice elenco articoli cercati /// /// /// /// public async Task> ArticoliGetSearch(int numRecord, string azienda, string searchVal) { return await Task.FromResult(dbController.ArticoliGetSearch(numRecord, azienda, searchVal)); } /// /// Aggiornamento record selezionato /// /// /// public async Task ArticoliUpdateRecord(AnagArticoli currRec) { return await dbController.ArticoliUpdateRecord(currRec); } /// /// Verifica se sia possiubile cancellare articolo dato suo CodArt cercando su redis o su /// tab veto da DB /// /// /// public bool ArticoloDelEnabled(object CodArt) { bool answ = false; string codArticolo = $"{CodArt}"; int cacheCheckArtUsato = 1; int.TryParse(_configuration.GetValue("ServerConf:cacheCheckArtUsato"), out cacheCheckArtUsato); TimeSpan TTLCache = TimeSpan.FromMinutes(cacheCheckArtUsato); // cerco in cache redis... string redKeyArtUsed = $"{Utils.redKeyArtUsed}:{codArticolo}"; string redKeyTabCheckArt = Utils.redKeyTabCheckArt; string rawData = redisDb.StringGet(redKeyArtUsed); if (!string.IsNullOrEmpty(rawData)) { bool.TryParse(rawData, out answ); } else { // controllo non sia stato mai prodotto sennò non posso cancellare... try { // cerco in cache se ci sia la tabella con gli articoli impiegati... string rawTable = redisDb.StringGet(redKeyTabCheckArt); List artList = new List(); if (!string.IsNullOrEmpty(rawTable)) { artList = JsonConvert.DeserializeObject>(rawTable); } // rileggo... if (artList == null || artList.Count == 0) { artList = new List(); var tabArticoli = dbController.ArticoliGetUsed(); var codList = tabArticoli.Select(x => x.CodArticolo); foreach (string cod in codList) { artList.Add(cod); } // SE fosse vuoto aggiungo comunque il cado "ND"... if (artList.Count == 0) { artList.Add("ND"); } // salvo rawTable = JsonConvert.SerializeObject(artList); redisDb.StringSet(redKeyTabCheckArt, rawTable, TTLCache); } // cerco nella tabella: se ci fosse --> disabilitato delete bool usato = false; if (artList != null && artList.Count > 0) { usato = artList.Contains(codArticolo); } answ = !usato; redisDb.StringSet(redKeyArtUsed, $"{answ}", TTLCache); } catch { } } return answ; } public async Task> ConfigGetAll() { int maxAgeConfig = 5; Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); List? result = new List(); // cerco in redis... RedisValue rawData = await redisDb.StringGetAsync(redisConfKey); if (!string.IsNullOrEmpty($"{rawData}")) { result = JsonConvert.DeserializeObject>($"{rawData}"); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"ConfigGetAll Read from REDIS: {ts.TotalMilliseconds}ms"); } else { result = await Task.FromResult(dbController.ConfigGetAll()); // serializzp e salvo... rawData = JsonConvert.SerializeObject(result); await redisDb.StringSetAsync(redisConfKey, rawData, TimeSpan.FromMinutes(maxAgeConfig)); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"ConfigGetAll Read from DB: {ts.TotalMilliseconds}ms"); } if (result == null) { result = new List(); } return result; } /// /// Reset dati cache config /// /// public async Task ConfigResetCache() { await redisDb.StringSetAsync(redisConfKey, ""); } /// /// Update chiave config /// /// public async Task ConfigUpdate(ConfigModel updRec) { return await Task.FromResult(dbController.ConfigUpdate(updRec)); } public void Dispose() { // Clear database controller dbController.Dispose(); redisConn.Dispose(); } /// /// Richiesta attivazione --> sposto avanti 1 minuto il periodo limite x fast running /// public void doActivate() { fastLimit = DateTime.Now.AddMinutes(1); } /// /// Restitusice elenco aziende /// /// public Task> ElencoAziende() { return Task.FromResult(dbController.AnagGruppiAziende()); } /// /// Restitusice elenco fasi /// /// public Task> ElencoGruppiFase() { return Task.FromResult(dbController.AnagGruppiFase()); } public Task> ElencoLink() { return Task.FromResult(dbController.ElencoLink()); } /// /// Elenco ultimi n record flux log dato macchina e flusso (ordinato x data registrazione) /// /// * = tutte, altrimenti solo x una data macchina /// *=tutti, altrimenti solo selezionato /// numero massimo record da restituire /// public async Task> FluxLogGetLastFilt(string IdxMacchina, string CodFlux, int MaxRec) { Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); var results = await Task.FromResult(dbController.FluxLogGetLastFilt(IdxMacchina, CodFlux, MaxRec)); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"FluxLogGetLastFilt | Read from DB: {ts.TotalMilliseconds}ms"); return results; } /// /// Elenco setup dei tag conf correnti /// /// public Task>> getAllTags() { return Task.FromResult(currTagConf); } /// /// restituisce il valore da REDIS associato al tag richeisto /// /// Chiave in cui cercare il valore /// public string getTagConf(string redKey) { string outVal = ""; // cerco in REDIS la conf x l'IOB var rawData = redisDb.StringGet(redKey); if (!string.IsNullOrEmpty(rawData)) { outVal = $"{rawData}"; } return outVal; } /// /// Elenco ODL filtrati x stato, articolo, KeyRich (che contiene stato) /// /// Stato ODL: true=in corso/completato /// Cod articolo /// KeyRich (parziale) da cercare (es cod stato x yacht) /// public async Task> ListODLFilt(bool inCorso, string codArt, string keyRichPart) { return await Task.FromResult(dbController.ListODLFilt(inCorso, codArt, keyRichPart)); } /// /// Elenco PODL non avviati filtrati x articolo, KeyRich (che contiene stato) /// /// Cod articolo /// KeyRich (parziale) da cercare (es cod stato x yacht) /// public async Task> ListPODLFilt(string codArt, string keyRichPart) { return await Task.FromResult(dbController.ListPODLFilt(codArt, keyRichPart)); } /// /// Elenco di tutte le macchine gestite /// /// public Task> MacchineGetAll() { return Task.FromResult(dbController.MacchineGetAll()); } /// /// Elenco ID macchine con dati FluxLog gestite /// /// public Task> MacchineWithFlux() { return Task.FromResult(dbController.MacchineWithFlux()); } public async Task> MseGetAll() { int maxAge = 2000; int.TryParse(_configuration.GetValue("ServerConf:maxAge"), out maxAge); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); List? result = new List(); // cerco in redis... RedisValue rawData = await redisDb.StringGetAsync(redisMseKey); if (!string.IsNullOrEmpty($"{rawData}")) { result = JsonConvert.DeserializeObject>($"{rawData}"); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"MseGetAll | Read from REDIS: {ts.TotalMilliseconds}ms"); } else { result = await Task.FromResult(dbController.MseGetAll(maxAge)); // serializzp e salvo... rawData = JsonConvert.SerializeObject(result); await redisDb.StringSetAsync(redisMseKey, rawData, TimeSpan.FromMilliseconds(maxAge)); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"MseGetAll | Read from DB: {ts.TotalMilliseconds}ms"); } if (result == null) { result = new List(); } return result; } /// /// Elenco di tutti i parametri filtrati x macchina /// /// * = tutte, altrimenti solo x una data macchina /// public async Task> ParametriGetFilt(string IdxMacchina) { List? result = new List(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string readType = "DB"; result = await Task.FromResult(dbController.ParametriGetFilt(IdxMacchina)); #if false // 2 minuti valore cache int maxAgeMin = 5; string currKey = $"{redisFluxByMac}:{IdxMacchina}"; // cerco in redis dato valore sel macchina... RedisValue rawData = await redisDb.StringGetAsync(currKey); if (!string.IsNullOrEmpty($"{rawData}")) { result = JsonConvert.DeserializeObject>($"{rawData}"); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"ParametriGetFilt | Read from REDIS: {ts.TotalMilliseconds}ms"); } else { result = await Task.FromResult(dbController.ParametriGetFilt(IdxMacchina)); // serializzp e salvo... rawData = JsonConvert.SerializeObject(result); await redisDb.StringSetAsync(currKey, rawData, TimeSpan.FromMinutes(maxAgeMin)); } if (result == null) { result = new List(); } #endif stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"ParametriGetFilt | Read from {readType}: {ts.TotalMilliseconds}ms"); return result; } /// /// Eliminazione record selezionato /// /// /// public async Task PODLDeleteRecord(PODLModel currRec) { return await dbController.PODLDeleteRecord(currRec); } /// /// Aggiornamento record selezionato /// /// /// public async Task POdlUpdateRecord(PODLModel currRec) { return await dbController.PODLUpdateRecord(currRec); } #endregion Public Methods #region Private Fields private static IConfiguration _configuration = null!; private static ILogger _logger = null!; private static System.Timers.Timer fastTimer = new System.Timers.Timer(4000); private static NLog.Logger Log = LogManager.GetCurrentClassLogger(); /// /// Limite in formato data-ora per inviare dati rapidamente (incrementato come now + 1 min /// ad ogni chiamata client) /// private DateTime fastLimit = DateTime.Now; private int fastRefreshMs = 1000; private string redisConfKey = "MP:MON:Cache:Config"; /// /// Oggetto per connessione a REDIS /// private ConnectionMultiplexer redisConn = null!; //ISubscriber sub = redis.GetSubscriber(); /// /// Oggetto DB redis da impiegare x chiamate R/W /// private IDatabase redisDb = null!; private string redisFluxByMac = "MP:MON:Cache:FluxByMac"; private string redisMseKey = "MP:MON:Cache:MSE"; private string redisStatoCom = "MP:MON:Cache:StatoCom"; private string redisTipoArt = "MP:MON:Cache:TipoArt"; #endregion Private Fields #region Private Methods private void ElapsedFastTimer(object? source, System.Timers.ElapsedEventArgs e) { var pUpd = Task.Run(async () => { // secondi pari --> blink, secondi dispari --> ricarica DateTime adesso = DateTime.Now; int resto = 0; Math.DivRem(adesso.Second, 2, out resto); if (resto == 0) { // invio in channel blink il segnale blinkPipe.sendMessage("true"); Log.Debug("Elapsed Fast Timer Blink"); } else { // invio in channel blink segnale false blinkPipe.sendMessage("false"); // rileggo dati... var newData = await MseGetAll(); // invio tramite la pipe... dataPipe.sendMessage(JsonConvert.SerializeObject(newData)); Log.Debug("Elapsed Fast Timer reload"); } }); pUpd.Wait(); } private void startTimers() { fastTimer = new System.Timers.Timer(fastRefreshMs); fastTimer.Elapsed += ElapsedFastTimer; fastTimer.Enabled = true; fastTimer.Start(); } /// /// Prova a caricare da file la conf degli IOB se presente /// private void tryLoadIobTags() { Dictionary> currConf = new Dictionary>(); string strExeFilePath = System.Reflection.Assembly.GetExecutingAssembly().Location; if (!string.IsNullOrEmpty(strExeFilePath)) { string? strWorkPath = Path.GetDirectoryName(strExeFilePath); if (!string.IsNullOrEmpty(strWorkPath)) { string filePath = $"{strWorkPath}/Conf/iobTagsConf.json"; if (File.Exists(filePath)) { string rawData = File.ReadAllText(filePath); if (!string.IsNullOrEmpty(rawData)) { var fileConfData = JsonConvert.DeserializeObject(rawData); if (fileConfData != null) { // effettuo esplosione conf SE contenesse il valore "***" = tutti // gli IOB if (fileConfData.IobSetup.ContainsKey("***")) { // recupero elenco macchine... var elencoMacc = dbController.MacchineGetAll(); // x ogni macchina creo le righe standard da conf... var baseConf = fileConfData.IobSetup.Where(x => x.Key == "***").FirstOrDefault(); foreach (var item in elencoMacc) { if (!string.IsNullOrEmpty(item.IdxMacchina)) { // converto i valori x la macchina corrente... clono in // nuovo oggetto var specVal = baseConf.Value.Select(i => i.Clone()).ToList(); // sostituisco segnaposto foreach (var singleVal in specVal) { singleVal.TagLocation = singleVal.TagLocation.Replace("***", item.IdxMacchina); } // ora aggiungo eventuali valori in override... if (fileConfData.IobSetup.ContainsKey(item.IdxMacchina)) { var otConf = fileConfData.IobSetup.Where(x => x.Key == item.IdxMacchina).FirstOrDefault(); //verifico x ogni valore other... foreach (var otTag in otConf.Value) { var ovrTag = specVal.Where(x => x.ColNum == otTag.ColNum && x.RowNum == otTag.RowNum).FirstOrDefault(); // se contiene --> sovrascrivo if (ovrTag != null) { //ovrTag = otTag.Clone(); specVal.Remove(ovrTag); specVal.Add(otTag.Clone()); } // se non contiene --> aggiungo else { specVal.Add(otTag); } } } currConf.Add(item.IdxMacchina, specVal); } } } // altrimenti copio ed ho finito else { currConf = fileConfData.IobSetup; } } } } if (currConf != null) { currTagConf = currConf; } } } } #endregion Private Methods } }