using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; using MP.TaskMan.Controllers; using MP.TaskMan.Models; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NLog; using StackExchange.Redis; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using static MP.TaskMan.Objects.Enums; namespace MP.TaskMan.Services { public class TaskService : BaseServ, IDisposable { #region Public Constructors /// /// Init servizio TAB /// /// public TaskService(IConfiguration configuration) { _configuration = configuration; // setup componenti REDIS redisConn = ConnectionMultiplexer.Connect(_configuration.GetConnectionString("Redis")); redisDb = redisConn.GetDatabase(); // ConnString del DB x gestione task string taskDbCS = _configuration.GetValue("SpecialConf:TaskManConn"); ConnStr = _configuration.GetConnectionString(taskDbCS); if (string.IsNullOrEmpty(ConnStr)) { Log.Error("ConnString empty!"); } else { StringBuilder sb = new StringBuilder(); MLController = new MpTaskController(configuration); sb.AppendLine($"TaskService | MpTaskController OK"); Log.Info(sb.ToString()); // sistemo i parametri x redHas... CodModulo = _configuration.GetValue("SpecialConf:CodModulo"); var cstringArray = ConnStr.Split(";"); foreach (var item in cstringArray) { var cData = item.Trim().Split("="); if (cData.Length == 2) { if (!connStrParams.ContainsKey(cData[0])) { connStrParams.Add(cData[0], cData[1]); } } } // sistemo DataSource = connStrParams["Server"]; DataBase = connStrParams["Database"]; string rbc = _configuration.GetValue("SpecialConf:RedisBaseConf"); if (string.IsNullOrEmpty(rbc)) { rbc = "MP:TaskMan"; } RedisBaseKey = $"{rbc}:{DataSource}:{DataBase}"; } // conf rest call service RCallService = new RestCallService(_configuration); } #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 void Dispose() { // Clear database controller MLController.Dispose(); // redis dispose redisConn = null; redisDb = null; } /// /// Chiamata esecuzione di un singolo task programmato /// /// Task richiesto /// Se true rischedula successiva chiamata /// public async Task ExecuteTask(TaskListModel TaskRec, bool SchedNext) { TaskResultModel answ = new TaskResultModel() { Task = $"TaskId: {TaskRec.TaskId} | {TaskRec.TType}", ExecResult = -1, TextResult = TaskRec.Enabled ? "Task Not Recognized" : "Task Not Enabled for Execution" }; // in primis verifica che SIA abilitato... if (TaskRec.Enabled) { // 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: answ = MLController.ExecuteSqlTask(TaskRec.TaskId, SchedNext); break; case Task2ExeType.RestCallGet: DateTime dtStart = DateTime.Now; // in primis testo la chiamata al servizio Health string rAnsw = await RCallService.CheckServer(); // se ok effettuo vera chiamata... if (rAnsw.ToUpper() == "OK") { var callResp = await RCallService.CallRestGet(TaskRec.Command, TaskRec.Args); DateTime dtEnd = DateTime.Now; string 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 answ = MLController.TaskExecSaveExecuted(TaskRec.TaskId, SchedNext, tExeMod); } break; default: break; } // svuoto cache! await FlushCacheAsync(); } return answ; } /// /// Pulizia cache Redis (tutta) /// /// public bool FlushCache() { RedisValue pattern = new RedisValue($"{RedisBaseKey}:*"); bool answ = ExecFlushRedisPattern(pattern); return answ; } /// /// Pulizia cache Redis per chiave specifica (da RedisBaseKey...) /// /// public bool FlushCache(string KeyReq) { RedisValue pattern = new RedisValue($"{RedisBaseKey}:{KeyReq}:*"); bool answ = ExecFlushRedisPattern(pattern); return answ; } /// /// Pulizia cache Redis (tutta) modalità Async /// /// public async Task FlushCacheAsync() { RedisValue pattern = new RedisValue($"{RedisBaseKey}:*"); bool answ = await ExecFlushRedisPatternAsync(pattern); return answ; } /// /// Pulizia cache Redis per chiave specifica (da RedisBaseKey...) modalità Async /// /// public async Task FlushCacheAsync(string KeyReq) { RedisValue pattern = new RedisValue($"{RedisBaseKey}:{KeyReq}:*"); bool answ = await ExecFlushRedisPatternAsync(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); } } public void rollBackEdit(object item) { MLController.RollBackEntity(item); } /// /// Ricerca task dato tipo + num max (desc) /// /// TaskId da cui deriva /// public async Task> TaskExecGetFilt(int TaskId, int maxRec, 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 = $"{RedisBaseKey}:ExecList:{TaskId}:{adesso:yyMMdd}:{adesso:HHmm}:{maxRec}"; RedisValue rawData = await redisDb.StringGetAsync(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}"); source = "REDIS"; } else { result = MLController.TaskExecGetFilt(TaskId, maxRec); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); await redisDb.StringSetAsync(currKey, rawData, FastCache); } if (result == null) { result = new List(); } sw.Stop(); Log.Debug($"TaskExecGetFilt | {source} | {sw.Elapsed.TotalMilliseconds}ms"); return result; } /// /// Elenco TaskList gestiti /// /// Tipo task da recuperare /// Abilitazioen cache REDIS /// Valore cercato /// public async Task> TaskListAll(Task2ExeType TType, bool enabRedis, 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; if (enabRedis) { string currKey = $"{RedisBaseKey}:List:{TType}"; RedisValue rawData = await redisDb.StringGetAsync(currKey); if (enabRedis && rawData.HasValue && rawData.Length() > 2) { result = JsonConvert.DeserializeObject>($"{rawData}"); source = "REDIS"; } else { result = MLController.TaskListGetAll(TType); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); await redisDb.StringSetAsync(currKey, rawData, FastCache); } } else { result = MLController.TaskListGetAll(TType); } 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($"TaskListAll | {source} | {sw.Elapsed.TotalMilliseconds}ms"); return result; } /// /// Update ordinamento task /// /// Record da spostare x priorità /// public async Task TaskListMove(TaskListModel rec2upd, bool moveUp) { bool dbResult = MLController.TaskListMove(rec2upd, moveUp); await Task.Delay(50); // svuoto cache! await FlushCacheAsync(); return dbResult; } /// /// Riordino record di un dato gruppo (da richiamare post cambio gruppo...) /// /// /// public async Task TaskListReorder(int codGroup) { bool dbResult = MLController.TaskListReorder(codGroup); await Task.Delay(50); // svuoto cache! await FlushCacheAsync(); return dbResult; } /// /// Update/Insert record TaskList /// /// /// public async Task TaskListUpsert(TaskListModel rec2upd) { bool dbResult = MLController.TaskListUpsert(rec2upd); await Task.Delay(50); // svuoto cache! await FlushCacheAsync(); return dbResult; } #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 IDatabase redisDb = null!; #endregion Protected Fields #region Private Fields private static Logger Log = LogManager.GetCurrentClassLogger(); private string CodModulo = ""; private string ConnStr = ""; private Dictionary connStrParams = new Dictionary(); #endregion Private Fields #region Private Properties private string DataBase { get; set; } = ""; private string DataSource { get; set; } = ""; private MpTaskController MLController { get; set; } = null!; private RestCallService RCallService { get; set; } = null!; private string RedisBaseKey { get; set; } = "MP:TASK"; #endregion Private Properties #region Private Methods /// /// Esegue flush memoria redis dato pat2Flush /// /// /// private bool 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 batch = new List(); foreach (var key in keys) { batch.Add(key); // Flush in batches of 1000 if (batch.Count >= 1000) { foreach (var item in batch) redisDb.KeyDelete(item); batch.Clear(); } } // Flush remaining keys foreach (var item in batch) redisDb.KeyDelete(item); } answ = true; #if 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; } } #endif // notifico update ai client in ascolto x reset cache NotifyReloadRequest($"FlushRedisCache | {pat2Flush}"); return answ; } /// /// Esegue flush memoria redis dato pat2Flush modalità Async /// /// /// private async Task ExecFlushRedisPatternAsync(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; #if 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) { await redisDb.KeyDeleteAsync(item); } answ = true; } } #endif // notifico update ai client in ascolto x reset cache NotifyReloadRequest($"FlushRedisCache | {pat2Flush}"); return answ; } #endregion Private Methods } }