using Maat.Core; using Maat.Data.Controllers; using Maat.Data.DbModels; using NLog; using StackExchange.Redis; using static Maat.Core.Enums; using System.Diagnostics; using Newtonsoft.Json; using System.Xml.Linq; using Newtonsoft.Json.Linq; namespace Maat.Data.Services { public class MsSqlTaskService : BaseServ, IDisposable { #region Public Constructors /// /// Servizio gestione Task su DB /// /// nome del thread /// stringa completa di connessione /// stringa completa REDIS /// url x chiamate api REST /// timeout esecuzione comandi sql (in minuti) public MsSqlTaskService(string threadName, string dbConnString, string redisConnString, string apiUrl, int execTimeOut) { tName = threadName; Log.Info($"{tName} | Starting MsSqlTaskService"); connString = dbConnString; // setup compoenti REDIS redisConn = ConnectionMultiplexer.Connect(redisConnString); redisDb = redisConn.GetDatabase(); // conf DB if (string.IsNullOrEmpty(connString)) { Log.Error($"{tName} | ConnString empty!"); } else { dbController = new MsSqlController(tName, dbConnString, execTimeOut); Log.Info($"{tName} | DbController OK"); } // conf rest call service RCallService = new RestCallService(apiUrl); } #endregion Public Constructors #region Public Events /// /// Evento richiesta rilettura dati pagina (x refresh pagine aperte) /// public event EventHandler ReloadRequest = delegate { }; #endregion Public Events #region Public Methods public List CheckExecute() { List answ = new List(); List listTask = TaskListAll(tName, Enums.Task2ExeType.ND, ""); // verifico SE ci siano task in scadenza... DateTime adesso = DateTime.Now; List task2exe = listTask.Where(x => x.Enabled && x.DtNextExec <= adesso).ToList(); int numDone = 0; if (task2exe != null && task2exe.Count > 0) { Log.Info($"{tName} | Found {task2exe.Count} task to execute"); // suddivisione x gruppi var ListGrouped = task2exe .OrderBy(x => x.Group) .ThenBy(x => x.Ordinal) .GroupBy(x => x.Group) .ToDictionary(g => g.Key, g => g.ToList()); // ciclo, e all'interno del gruppo SE non passa uno step si ferma.... foreach (var taskGroup in ListGrouped) { foreach (var taskRec in taskGroup.Value) { TaskResultModel result = ExecuteTask(taskRec); answ.Add(result); numDone++; // SE non eseguito --> esce, come anche se passato timeout (e ultima esecuzione è prima della scadenza.. if (result.ExecResult < 0) { break; } } } #if false foreach (var taskRec in task2exe) { TaskResultModel result = ExecuteTask(taskRec); answ.Add(result); } #endif } else { Log.Trace($"{tName} | Chiamata CheckExecute | eseguiti {numDone} task"); } // resituisco return answ; } public string CheckRestServer() { return RCallService.CheckServer(); } public void Dispose() { // Clear database controller dbController.Dispose(); // redis dispose redisConn.Dispose(); } /// /// Chiamata esecuzione di un singolo task programmato /// /// Task richiesto /// public TaskResultModel ExecuteTask(TaskListModel TaskRec) { TaskResultModel dbResult = new TaskResultModel() { Task = $"TaskId: {TaskRec.TaskId} | {TaskRec.TType}", ExecResult = -1, TextResult = "Task Not recognized" }; // verifico tipo di task ed eseguo di conseguenza... switch (TaskRec.TType) { //case Task2ExeType.ND: // break; //case Task2ExeType.Exe: // break; //case Task2ExeType.SqlCommand: // break; case Task2ExeType.SqlStored: dbResult = dbController.ExecuteSqlTask(TaskRec.TaskId, true); Log.Info($"{tName} | Executed | TaskRec: {TaskRec}"); break; case Task2ExeType.RestCallGet: // in primis testo la chiamata al servizio Health string rAnsw = RCallService.CheckServer(); DateTime dtStart = DateTime.Now; // se ok effettuo vera chiamata... if (rAnsw.ToUpper() == "OK") { var callResp = RCallService.CallRestGet(TaskRec.Command, TaskRec.Args); DateTime dtEnd = DateTime.Now; string formattedJson = ""; if (!string.IsNullOrEmpty(callResp.Content)) { formattedJson = JValue.Parse(callResp.Content).ToString(Formatting.Indented); } TaskExecModel tExeMod = new TaskExecModel() { DtEnd = dtEnd, DtStart = dtStart, IsError = callResp.StatusCode != System.Net.HttpStatusCode.OK, TaskId = TaskRec.TaskId, // deserializzazione come json indentato?!? Result = formattedJson// $"{callResp.Content}".Replace("\"", ""), }; // salvo su DB dbResult = dbController.TaskExecSaveExecuted(TaskRec.TaskId, true, tExeMod); } break; default: break; } // svuoto cache! FlushCache("Task"); return dbResult; } /// /// Pulizia cache Redis (tutta) /// /// public bool FlushCache() { RedisValue pattern = new RedisValue($"{Const.RedisBaseKey}:*"); bool answ = ExecFlushRedisPattern(pattern); return answ; } /// /// Pulizia cache Redis per chiave specifica (da redisBaseKey...) /// /// public bool FlushCache(string KeyReq) { RedisValue pattern = new RedisValue($"{Const.RedisBaseKey}:{KeyReq}:*"); bool answ = ExecFlushRedisPattern(pattern); return answ; } /// /// Invio notifica rilettura (con parametro) /// /// public void NotifyReloadRequest(string message) { if (ReloadRequest != null) { // messaggio ReloadEventArgs rea = new ReloadEventArgs(message); ReloadRequest.Invoke(this, rea); } } /// /// Elenco TaskList gestiti /// /// /// /// public List TaskListAll(string threadName, Task2ExeType TType, string searchVal = "") { // setup parametri costanti string source = "DB"; Stopwatch sw = new Stopwatch(); sw.Start(); List result = new List(); // cerco in redis... DateTime adesso = DateTime.Now; string currKey = $"{Const.RedisBaseKey}:Task:{threadName.Replace("_", ":")}:List:{TType}"; RedisValue rawData = redisDb.StringGet(currKey); if (false && rawData.HasValue && rawData.Length() > 4) { var rawResult = JsonConvert.DeserializeObject>($"{rawData}"); result = rawResult ?? new List(); source = "REDIS"; } else { result = dbController.TaskListGetAll(TType); // serializzp e salvo... rawData = JsonConvert.SerializeObject(result); redisDb.StringSet(currKey, rawData, FastCache); } if (result == null) { result = new List(); } // se necessario filtro.. if (!string.IsNullOrEmpty(searchVal)) { result = result .Where(x => x.Name.Contains(searchVal, StringComparison.InvariantCultureIgnoreCase) || x.Descript.Contains(searchVal, StringComparison.InvariantCultureIgnoreCase)) .ToList(); } sw.Stop(); Log.Debug($"{tName} | TaskListAll | {source} | {sw.Elapsed.TotalMilliseconds}ms"); return result; } #endregion Public Methods #region Protected Fields /// /// Oggetto per connessione a REDIS /// protected ConnectionMultiplexer redisConn = null!; /// /// Oggetto DB redis da impiegare x chiamate R/W /// protected StackExchange.Redis.IDatabase redisDb = null!; #endregion Protected Fields #region Private Fields private static Logger Log = LogManager.GetCurrentClassLogger(); private string connString = ""; private string tName = ""; #endregion Private Fields #region Private Properties private MsSqlController dbController { get; set; } = null!; private RestCallService RCallService { get; set; } = null!; #endregion Private Properties #region Private Methods /// /// Esegue flush memoria redis dato pattern /// /// /// private bool ExecFlushRedisPattern(RedisValue pattern) { bool answ = false; var listEndpoints = redisConn.GetEndPoints(); foreach (var endPoint in listEndpoints) { //var server = redisConnAdmin.GetServer(listEndpoints[0]); var server = redisConn.GetServer(endPoint); if (server != null) { var keyList = server.Keys(redisDb.Database, pattern); foreach (var item in keyList) { redisDb.KeyDelete(item); } answ = true; } } // notifico update ai client in ascolto x reset cache NotifyReloadRequest($"{tName} | FlushRedisCache | {pattern}"); return answ; } #endregion Private Methods } }