using Core;
using Core.DTO;
using LiMan.DB;
using LiMan.DB.DBModels;
using LiMan.DB.DTO;
using LiMan.DB.Services;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Win32;
using Newtonsoft.Json;
using NLog;
using StackExchange.Redis;
using StackExchange.Redis.Extensions.Core.Abstractions;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static Core.Enum;
namespace LiMan.APi.Data
{
///
/// Classe astrazione accesso dati
///
public class ApiDataService : CommonDataServices
{
#region Public Constructors
///
/// Init classe
///
///
///
///
///
///
public ApiDataService(IConfiguration configuration, ILogger logger, IEmailSender emailSender, IRedisCacheClient redisCacheClient, IConnectionMultiplexer redisConnMult) : base(configuration, redisConnMult, emailSender)
{
_configuration = configuration;
#if false
_logger = logger;
_emailSender = emailSender;
_redisCacheClient = redisCacheClient;
//this.distributedCache = distributedCache;
#endif
#if false
// Conf cache
redisConn = redisConnMult;
redisDb = this.redisConn.GetDatabase();
// json serializer... FIX errore loop circolare https://www.ryadel.com/en/jsonserializationexception-self-referencing-loop-detected-error-fix-entity-framework-asp-net-core/
JSSettings = new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
// conf messagepipe: setup canali pub/sub
EnrollMessPipe = new MessagePipe(redisConn, Const.ENRL_MSG_PIPE);
TaskMessPipe = new MessagePipe(redisConn, Const.TASK_MSG_PIPE);
UpdActMessPipe = new MessagePipe(redisConn, Const.UPDT_MSG_PIPE);
// conf DB
string connStrDB = _configuration.GetConnectionString("LiMan.DB");
if (string.IsNullOrEmpty(connStrDB))
{
Log.Error("ConnString empty!");
}
else
{
dbController = new LiMan.DB.Controllers.DbController(configuration);
Log.Info("DbController OK");
}
#endif
}
#endregion Public Constructors
#region Public Methods
///
/// Recupero da Redis info x elenco info dettaglio di un SINGOLO EgwACC
///
/// Cod univoco impegno
public Dictionary EgwAccDictDetailGet(string CodImp)
{
RedisKey reqKey = (RedisKey)$"{rKeyEACCDetail}:{CodImp}";
Dictionary answ = redisHashDictGet(reqKey);
return answ;
}
///
/// Salvataggio in Redis info di dettaglio di un EgwACC
///
/// Cod univoco impegno
///
public void EgwAccDictDetailSet(string CodImp, Dictionary Payload)
{
// salvo in hash info CodImp + versione
RedisKey reqKey = (RedisKey)$"{rKeyEACCDetail}:{CodImp}";
// imposto scadenza a 60gg...
double scadenza = 60 * 24 * 60;
redisHashDictSet(reqKey, Payload, scadenza);
#if false
// salvo in altra hash (con key da CodImp) le info specifiche ricevute
foreach (var res in Payload)
{
redisHashKeySet(reqKey, res.Key, res.Value, scadenza);
}
#endif
// invio string in messagepipe x forzare refresh...
UpdActMessPipe.sendMessage(CodImp);
}
///
/// recupero da Redis info x elenco EgwACC secondo key richiesta
///
/// Chiave Richiesta (es Vers, Last, ...)
public Dictionary EgwAccDictValGet(string KeyName)
{
RedisKey reqKey = (RedisKey)$"{rKeyEACCList}:{KeyName}";
Dictionary answ = redisHashDictGet(reqKey);
return answ;
}
///
/// Salvataggio in Redis info x elenco EgwACC sul campo
///
/// Cod univoco impegno
/// Chiave salvata (es Vers, Last, ...)
/// Valore serializzato
public void EgwAccDictValSet(string CodImp, string KeyName, string Value)
{
// salvo in hash info CodImp + versione
RedisKey reqKey = (RedisKey)$"{rKeyEACCList}:{KeyName}";
// salvo in altra hash (con key da CodImp) le info specifiche ricevute
redisHashKeySet(reqKey, CodImp, Value);
// invio string in messagepipe x forzare refresh...
UpdActMessPipe.sendMessage(CodImp);
}
///
/// Crea record richiesta enroll (univoco rispetto richieste correnti)
///
/// Dati da associare alla richeista
///
public async Task EnrollReqCreate(Dictionary MachineInfo)
{
Stopwatch sw = new Stopwatch();
sw.Start();
// svuoto elenco richieste scaduteù
var cleanDone = await EnrollReqPurgeInvalid();
// prendo elenco attive x evitare duplicazioni codici TOTP...
var resList = await EnrollReqGetActive();
int totpCode = rnd.Next(1, 100000000);
// verifico che non sia preesistente...
if (resList.Count > 0)
{
// cerco se fosse già presente...
while (resList.Where(x => x.Passcode == totpCode).Any())
{
// genero nuovo...
await Task.Delay(rnd.Next(50));
totpCode = rnd.Next(1, 100000000);
}
}
// serializzo i dati della richiesta..
string reqPayload = JsonConvert.SerializeObject(MachineInfo);
// preparo il record da registrare...
EnrollRequestModel newReq = new EnrollRequestModel()
{
DtReq = DateTime.Now,
Passcode = totpCode,
ReqPayload = reqPayload
};
var dbRec = await EnrollReqUpsert(newReq);
sw.Stop();
TimeSpan ts = sw.Elapsed;
Log.Trace($"Effettuata EnrollReqCreate: {ts.TotalMilliseconds} ms");
// invio string in messagepipe x forzare refresh...
EnrollMessPipe.sendMessage("NewEnrollReq");
// restituisce record
return dbRec;
}
///
/// Esegue aggiunta file dato ticket e list uploadResult
///
/// Identificativo del ticket
/// Directory di salvataggio dei file
/// lista risultati della funzione di upload
///
public async Task FileAdd(int idxTicket, string baseDir, List fileUploaded)
{
bool fatto = false;
// inserimento!
Stopwatch sw = new Stopwatch();
sw.Start();
fatto = dbController.FileAdd(idxTicket, baseDir, fileUploaded);
sw.Stop();
TimeSpan ts = sw.Elapsed;
Log.Trace($"Effettuata inserimento con FileAdd: {ts.TotalMilliseconds} ms");
// restituisce elenco
return await Task.FromResult(fatto);
}
///
/// Effettua registrazione chiamata verificando se vada messa sul DB o in redis...
///
///
///
///
public async Task recordCall(string codInst, string codApp, string targetUrl)
{
bool fatto = false;
// in primis recupero statistiche (e nel mentre eventualmente salvo su DB)
SampleStats currStats = await getCurrStats();
// preparo chiave x dato da loggare
string currKey = $"{rKeySampleVars}:{codInst}:{codApp}:{targetUrl}";
// verifico presenza contatore corrente altrimenti aggiungo e salvo...
if (!currStats.VList.Contains(currKey))
{
currStats.VList.Add(currKey);
// salvo!
await setCurrStats(currStats);
}
// incremento valore contatore corrente
await redCountIncr(currKey);
return fatto;
}
///
/// Effettua registrazione da codice chiave...
///
///
///
public async Task recordCall(string chiave, string targetUrl)
{
bool fatto = false;
// valutare se cache key --> lic...
if (!string.IsNullOrEmpty(chiave))
{
var currLic = await LicenzaByMasterKey(chiave);
if (currLic != null)
{
fatto = await recordCall(currLic.CodInst, currLic.CodApp, targetUrl);
}
}
return fatto;
}
///
/// Elenco Release dato Applicativo
///
/// Codice Applicazione
///
public async Task> ReleaseDtoGetByApp(string CodApp)
{
await Task.Delay(1);
List dbResult = new List();
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
dbResult = dbController.ReleaseDtoGetByApp(CodApp);
stopWatch.Stop();
TimeSpan ts = stopWatch.Elapsed;
Log.Trace($"Effettuata lettura da DB per ReleaseDtoGetByApp | {CodApp} | {ts.TotalMilliseconds} ms");
return dbResult;
}
///
/// Elenco Release dato Applicativo + versione minima
///
/// Codice Applicazione
/// Versione minima richiesta
///
public async Task> ReleaseDtoGetByAppVers(string CodApp, string VersMin)
{
await Task.Delay(1);
List dbResult = new List();
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
dbResult = dbController.ReleaseDtoGetByAppVers(CodApp, VersMin);
stopWatch.Stop();
TimeSpan ts = stopWatch.Elapsed;
Log.Trace($"Effettuata lettura da DB per ReleaseDtoGetByAppVers | {CodApp} | vers >= {VersMin} | {ts.TotalMilliseconds} ms");
return dbResult;
}
///
/// Elenco Release dato Applicativo + versione minima
///
/// Codice Applicazione
/// Versione minima richiesta
/// Versione massima consentita
///
public async Task> ReleaseGetByAppVersLimit(string CodApp, string VersMin, string VersMax)
{
await Task.Delay(1);
List dbResult = new List();
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
dbResult = dbController.ReleaseDtoGetByAppVersLimit(CodApp, VersMin, VersMax);
stopWatch.Stop();
TimeSpan ts = stopWatch.Elapsed;
Log.Trace($"Effettuata lettura da DB per ReleaseDtoGetByAppVersLimit | {CodApp} | vers >= {VersMin} | {ts.TotalMilliseconds} ms");
return dbResult;
}
///
/// Elenco di TUTTE le release CRITICAL come dizionario CodApp - release
///
///
public async Task> ReleaseGetCritical()
{
Dictionary dbResult = new Dictionary();
Stopwatch sw = new Stopwatch();
sw.Start();
string cacheKey = $"{rKeyAttivByLic}:AppReleases:CRITICAL";
string source = "REDIS";
trackCache(cacheKey);
string rawData = await getRSV(cacheKey);
if (!string.IsNullOrEmpty(rawData))
{
dbResult = JsonConvert.DeserializeObject>(rawData);
}
else
{
source = "DB";
dbResult = dbController.ReleaseGetCritical();
rawData = JsonConvert.SerializeObject(dbResult);
await setRSV(cacheKey, rawData, redCacheTtlFast);
}
sw.Stop();
Log.Trace($"Effettuata lettura da DB per ReleaseGetCritical | {source} | # found: {dbResult.Count} | {sw.ElapsedMilliseconds} ms");
return dbResult;
}
///
/// Registro su DB le statistiche delle chiavi in elenco, resettando i vari contatori quando res
///
/// Elenco key nel formato {rKeySampleVars}:{codInst}:{codApp}:{targetUrl}
public async Task saveStatsToDb(List keyList)
{
bool fatto = false;
// ciclo x eseguire 1:1
foreach (var item in keyList)
{
// recupero counter...
var currCount = await redCount(item);
// scompongo key... senza url di base
string[] valStr = item.Replace($"{rKeySampleVars}:", "").Split(":");
if (valStr.Length > 2)
{
LogCallModel newRec = new LogCallModel()
{
CodInst = valStr[0],
CodApp = valStr[1],
TargetUrl = item.Replace($"{rKeySampleVars}:", "").Replace($"{valStr[0]}:{valStr[1]}:", ""),
DataRif = DateTime.Now,
NumCall = currCount
};
fatto = await dbController.LogCallUpsert(newRec);
if (fatto)
{
await redCountClear(item);
}
}
}
return fatto;
}
///
/// Restituisce i task associati ad un dato EgwACC
///
///
///
public Dictionary TaskListGet(string CodImp)
{
RedisKey currKey = (RedisKey)$"{rKeyTaskReq}:{CodImp}";
Dictionary answ = redisHashDictGet(currKey);
return answ;
}
///
/// Resetta i task di un dato EgwACC
///
///
///
public Dictionary TaskListReset(string CodImp)
{
Dictionary answ = new Dictionary();
// svuoto dizoinari specifici
bool ok01 = redisHashDictDelete((RedisKey)$"{rKeyTaskReq}:{CodImp}");
bool ok02 = redisHashDictDelete((RedisKey)$"{rKeyTaskRun}:{CodImp}");
bool ok03 = redisHashDictDelete((RedisKey)$"{rKeyTaskDone}:{CodImp}");
// svuoto in elenco task x imp...
redisHashKeyDelete((RedisKey)$"{rKeyTaskReq}List", CodImp);
redisHashKeyDelete((RedisKey)$"{rKeyTaskRun}List", CodImp);
redisHashKeyDelete((RedisKey)$"{rKeyTaskDone}List", CodImp);
// leggo remaining x verificare sia ok..
var dictReq = new Dictionary(redisHashDictGet((RedisKey)$"{rKeyTaskReq}:{CodImp}"));
var dictRun = new Dictionary(redisHashDictGet((RedisKey)$"{rKeyTaskRun}:{CodImp}"));
var dictDone = new Dictionary(redisHashDictGet((RedisKey)$"{rKeyTaskDone}:{CodImp}"));
answ = new Dictionary(dictReq);
foreach (var item in dictRun)
{
answ.TryAdd(item.Key, item.Value);
}
foreach (var item in dictDone)
{
answ.TryAdd(item.Key, item.Value);
}
// invio string in messagepipe x forzare refresh...
UpdActMessPipe.sendMessage(CodImp);
return answ;
}
///
/// Salva il risultato dell'esecuzione dei task effettuata
///
///
///
///
///
public int TaskSetDone(string CodImp, Dictionary DataPayload)
{
int done = 0;
// imposto scadenza a 30gg...
double scadenza = 60 * 24 * 30;
// upsert per ogni singolo record + rimozione da running
foreach (var res in DataPayload)
{
bool answ = redisHashKeySet((RedisKey)$"{rKeyTaskDone}:{CodImp}", res.Key, res.Value, scadenza);
redisHashKeyDelete((RedisKey)$"{rKeyTaskRun}:{CodImp}", res.Key);
// sistemo hashset...
redisHashKeySet((RedisKey)$"{rKeyTaskDone}List", CodImp, $"{DateTime.Now:yyyy-MM-dd HH:mm:ss}");
redisHashKeyDelete((RedisKey)$"{rKeyTaskRun}List", CodImp);
// segnalo fatto
done += answ ? 1 : 0;
}
// invio string in messagepipe x forzare refresh...
UpdActMessPipe.sendMessage(CodImp);
return done;
}
///
/// Salva come running i task indicati (togliendo da richiesti)
///
///
///
///
///
public int TaskSetRunning(string CodImp, Dictionary DataPayload)
{
int done = 0;
// imposto scadenza a 30gg...
double scadenza = 60 * 24 * 30;
// upsert per ogni singolo record + rimozione da richiesti
foreach (var res in DataPayload)
{
// sposto il valore in key
bool answ = redisHashKeySet((RedisKey)$"{rKeyTaskRun}:{CodImp}", res.Key, res.Value, scadenza);
redisHashKeyDelete((RedisKey)$"{rKeyTaskReq}:{CodImp}", res.Key);
// sistemo list task...
redisHashKeySet((RedisKey)$"{rKeyTaskRun}List", CodImp, $"{DateTime.Now:yyyy-MM-dd HH:mm:ss}");
redisHashKeyDelete((RedisKey)$"{rKeyTaskReq}List", CodImp);
// segnalo fatto
done += answ ? 1 : 0;
}
// invio string in messagepipe x forzare refresh...
UpdActMessPipe.sendMessage(CodImp);
return done;
}
///
/// Esegue aggiunta Ticket richiesto + restitusice aperti x cliente
///
///
///
///
public async Task TicketAdd(SupportRequest currRequest)
{
bool fatto = false;
// inserimento!
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
fatto = dbController.TicketAddNew(currRequest);
stopWatch.Stop();
TimeSpan ts = stopWatch.Elapsed;
Log.Trace($"Effettuata inserimento con TicketAdd: {ts.TotalMilliseconds} ms");
// restituisce elenco
return await Task.FromResult(fatto);
}
///
/// Elenco ticket dato cliente + App + MasterKey
///
///
///
///
///
///
public async Task> TicketByCliente(string CodInst, string CodApp, string MasterKey, int numRec = 1000)
{
List dbResult = new List();
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
dbResult = dbController.TicketGetFilt(false, TipologiaTicket.ND, CodApp, CodInst, MasterKey, numRec);
stopWatch.Stop();
TimeSpan ts = stopWatch.Elapsed;
Log.Trace($"Effettuata lettura da DB per TicketByCliente: {ts.TotalMilliseconds} ms");
return await Task.FromResult(dbResult);
}
///
/// Salva ultima dataora di comunicazione con un updater
///
///
///
///
public int UpdaterRecordAction(string CodImp, string CodAction)
{
int done = 0;
// salvo info sul TIPO di chiamata
bool answ = redisHashKeyIncDecBy((RedisKey)$"{rKeyUpdLastAct}:{CodImp}", CodAction, 1);
// registro ultima chiamata
redisHashKeySet((RedisKey)$"{rKeyUpdLastAct}List", CodImp, $"{DateTime.Now:yyyy-MM-dd HH:mm:ss}");
// invio string in messagepipe x forzare refresh...
UpdActMessPipe.sendMessage(CodImp);
// scambio tipo di pipe messaggi (update e non task)
//TaskMessPipe.sendMessage(CodImp);
return done;
}
#endregion Public Methods
#region Protected Fields
protected new static Logger Log = LogManager.GetCurrentClassLogger();
#endregion Protected Fields
#region Protected Methods
///
/// Recupera statistiche correnti
///
///
protected async Task getCurrStats()
{
DateTime adesso = DateTime.Now;
SampleStats answ = new SampleStats()
{
Name = "ApiStats"
};
// in primis check data/ora prima/ultima scrittura del set... (2 date, lista chiavi gestite)
string rawData = await getRSV(rKeySampleStats);
if (rawData != null)
{
answ = JsonConvert.DeserializeObject(rawData);
// aggiorno ultimo controllo e salvo...
answ.DtLast = adesso;
// salvo!
await setCurrStats(answ);
}
// controllo se scadute...
if (adesso.Subtract(answ.DtFirst).TotalMinutes > 60)
{
// se scaduto --> registrazione set sul DB (async), resettando i vari contatori...
bool salvato = await saveStatsToDb(answ.VList);
// inizio NUOVO set vuoto con record corrente
answ = new SampleStats()
{
Name = "ApiStats"
};
// salvo!
await setCurrStats(answ);
}
// restituisco record!
return answ;
}
///
/// Salva statistiche correnti
///
///
protected async Task setCurrStats(SampleStats newVal)
{
bool answ = false;
string rawData = JsonConvert.SerializeObject(newVal);
answ = await setRSV(rKeySampleStats, rawData, 24 * hourTTL);
return answ;
}
#endregion Protected Methods
}
}