4cde37cd5f
Rimesso bottone x export 3d con timeout x risposta calcolo
1678 lines
61 KiB
C#
1678 lines
61 KiB
C#
using Microsoft.AspNetCore.Identity.UI.Services;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Newtonsoft.Json;
|
|
using NLog;
|
|
using NLog.LayoutRenderers.Wrappers;
|
|
using StackExchange.Redis;
|
|
using System.Diagnostics;
|
|
using WebDoorCreator.Core;
|
|
using WebDoorCreator.Data.DTO;
|
|
|
|
namespace WebDoorCreator.Data.Services
|
|
{
|
|
public class QueueDataService : IDisposable
|
|
{
|
|
#region Public Constructors
|
|
|
|
/// <summary>
|
|
/// Init classe
|
|
/// </summary>
|
|
/// <param name="configuration"></param>
|
|
/// <param name="emailSender"></param>
|
|
/// <param name="redisConn"></param>
|
|
public QueueDataService(IConfiguration configuration, IEmailSender emailSender, IConnectionMultiplexer redisConn)
|
|
{
|
|
Log.Info("QueueDataService starting...");
|
|
_configuration = configuration;
|
|
_emailSender = emailSender;
|
|
// setup componenti REDIS
|
|
var redConnString = _configuration.GetConnectionString("Redis");
|
|
if (redConnString == null)
|
|
{
|
|
Log.Error("REDIS ConnString empty!");
|
|
}
|
|
else
|
|
{
|
|
this.redisConn = ConnectionMultiplexer.Connect(redConnString);
|
|
redisDb = this.redisConn.GetDatabase();
|
|
}
|
|
// setup canali pub/sub
|
|
CalcReqPipe = new MessagePipe(redisConn, Constants.CALC_REQ_QUEUE);
|
|
CalcDonePipe = new MessagePipe(redisConn, Constants.CALC_DONE_QUEUE);
|
|
// 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
|
|
};
|
|
// leggo conf speciali
|
|
vetoRemoveProcSec = _configuration.GetValue<int>("RuntimeOpt:VetoRemoveProcessing");
|
|
logTimingEnable = configuration.GetValue<bool>("RuntimeOpt:LogTimingEnable");
|
|
statSampleSize = configuration.GetValue<int>("RuntimeOpt:StatSampleSize");
|
|
// fix dati img missing...
|
|
missingFilePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/images", "MissingOrange.svg");
|
|
if (File.Exists(missingFilePath))
|
|
{
|
|
missingSvgContent = File.ReadAllText(missingFilePath);
|
|
}
|
|
Log.Info("QueueDataService started!");
|
|
}
|
|
|
|
#endregion Public Constructors
|
|
|
|
#region Public Properties
|
|
|
|
/// <summary>
|
|
/// Message pipe esecuzione elaborazione CAM --> UI
|
|
/// </summary>
|
|
public MessagePipe CalcDonePipe { get; set; } = null!;
|
|
|
|
/// <summary>
|
|
/// Message pipe richieste elaborazione UI --> CAM
|
|
/// </summary>
|
|
public MessagePipe CalcReqPipe { get; set; } = null!;
|
|
|
|
#endregion Public Properties
|
|
|
|
#region Public Methods
|
|
|
|
public void Dispose()
|
|
{
|
|
redisConn.Dispose();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua pulizia cache dati calcolo porte
|
|
/// </summary>
|
|
/// <param name="DoorId"></param>
|
|
/// <returns></returns>
|
|
public async Task<bool> DoorCalcCacheCleanup()
|
|
{
|
|
/*----------------------------------
|
|
* Recupero Rev corrente porta o inizializza cercando
|
|
* - coda calcoli pending
|
|
* - coda errori
|
|
* - coda calcoli eseguiti
|
|
*
|
|
* se non trova riparte da 1
|
|
*
|
|
----------------------------------*/
|
|
bool fatto = false;
|
|
int numDayPre = 30;
|
|
if (!string.IsNullOrEmpty(_configuration.GetValue<string>("RuntimeOpt:MaxDayCalcCache")))
|
|
{
|
|
numDayPre = _configuration.GetValue<int>("RuntimeOpt:MaxDayCalcCache");
|
|
}
|
|
DateTime dtLimite = DateTime.Now.AddDays(-numDayPre);
|
|
// recupero elenco di tutti i refresh porte...
|
|
var refreshStatus = await DoorRefreshStatus();
|
|
// ciclo su tutti e cerco quelli + vecchi di x giorni
|
|
if (refreshStatus != null && refreshStatus.Count > 0)
|
|
{
|
|
DateTime dtCurr = DateTime.Now;
|
|
foreach (var item in refreshStatus)
|
|
{
|
|
// confronto data-ora della singola porta...
|
|
if (!string.IsNullOrEmpty(item.Key))
|
|
{
|
|
try
|
|
{
|
|
var rawDate = JsonConvert.DeserializeObject<DateTime>(item.Value);
|
|
// se esiste e SE è scaduto
|
|
if (rawDate > DateTime.MinValue && rawDate < dtLimite)
|
|
{
|
|
// cancello i dati!
|
|
await RequestProcessingRemove(item.Key);
|
|
await RequestErrRemove(item.Key);
|
|
await RequestDoneRemove(item.Key);
|
|
await DoorRefreshRemove(item.Key);
|
|
}
|
|
}
|
|
catch
|
|
{ }
|
|
}
|
|
}
|
|
}
|
|
await Task.Delay(1);
|
|
return fatto;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Restitusice gli errori della porta in oggetto
|
|
/// </summary>
|
|
/// <param name="doorIdVers">formato DoorId:Versione</param>
|
|
/// <returns></returns>
|
|
public async Task<bool> DoorErrExists(string doorIdVers)
|
|
{
|
|
bool hasErr = false;
|
|
// cerco versione errore x la porta...
|
|
var doorData = doorIdVers.Split(":");
|
|
if (doorData.Length == 2)
|
|
{
|
|
RedisKey currErrKey = new RedisKey($"{Constants.CALC_REQ_ERRS}");
|
|
int currId = await RedHashGetInt(currErrKey, doorData[0]);
|
|
hasErr = doorData[1] == $"{currId}";
|
|
}
|
|
return hasErr;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Restitusice gli errori della porta in oggetto
|
|
/// </summary>
|
|
/// <param name="doorIdVers">formato DoorId:Versione</param>
|
|
/// <returns></returns>
|
|
public async Task<string> DoorErrGet(string doorIdVers)
|
|
{
|
|
RedisKey currErrKey = new RedisKey($"{Constants.CALC_REQ_ERRS}:{doorIdVers}");
|
|
RedisValue errVal = await redisDb.StringGetAsync(currErrKey);
|
|
return errVal.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cerca nelle esecuzioni ultimo SVG della porta e lo restituisce...
|
|
/// </summary>
|
|
/// <param name="DoorId"></param>
|
|
/// <returns></returns>
|
|
public async Task<string> DoorGetLastSvg(int DoorId)
|
|
{
|
|
string answ = "";
|
|
// cerco in hashtable dei task eseguiti ultimo x porta corrente...
|
|
var doorData = await RequestDoneGetSingle(DoorId);
|
|
if (doorData != null && doorData.Count > 0)
|
|
{
|
|
var firstRecord = doorData.FirstOrDefault();
|
|
// recupero da REDIS
|
|
var currSvgKey = new RedisKey($"{Constants.CALC_REQ_SVG_CACHE}:{firstRecord.Key}:{firstRecord.Value}");
|
|
var rawData = await redisDb.StringGetAsync(currSvgKey);
|
|
if (rawData.HasValue)
|
|
{
|
|
answ = $"{rawData}";
|
|
}
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Restituisce SVG dell'immagine missing...
|
|
/// </summary>
|
|
/// <param name="DoorId"></param>
|
|
/// <returns></returns>
|
|
public string DoorGetMissingSvg()
|
|
{
|
|
string answ = missingSvgContent;
|
|
if (string.IsNullOrEmpty(answ))
|
|
{
|
|
// fix dati img missing...
|
|
missingFilePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/images", "MissingOrange.svg");
|
|
if (File.Exists(missingFilePath))
|
|
{
|
|
missingSvgContent = File.ReadAllText(missingFilePath);
|
|
answ = missingSvgContent;
|
|
}
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cerca nei VetoProc se ci sia x porta corrente
|
|
/// </summary>
|
|
/// <param name="DoorId"></param>
|
|
/// <returns></returns>
|
|
public async Task<string> DoorProcVetoGetAsync(int DoorId)
|
|
{
|
|
string answ = "";
|
|
// recupero da REDIS
|
|
var currVetoKey = new RedisKey($"{Constants.CALC_REQ_VETO_REC}:{DoorId}");
|
|
var rawData = await redisDb.StringGetAsync(currVetoKey);
|
|
if (rawData.HasValue)
|
|
{
|
|
answ = $"{rawData}";
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// imposta un VetoProc x porta corrente x 1 sec
|
|
/// </summary>
|
|
/// <param name="DoorId">ID porta da vietare x ricalcolo</param>
|
|
/// <param name="msVeto">Durata veto in ms</param>
|
|
/// <returns></returns>
|
|
public async Task DoorProcVetoSetAsync(int DoorId, int msVeto)
|
|
{
|
|
// recupero da REDIS
|
|
var currVetoKey = new RedisKey($"{Constants.CALC_REQ_VETO_REC}:{DoorId}");
|
|
var rawData = await redisDb.StringSetAsync(currVetoKey, $"{DoorId}", TimeSpan.FromMilliseconds(msVeto));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove for single hash record
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public async Task<bool> DoorRefreshRemove(string doorId)
|
|
{
|
|
RedisKey currKey = new RedisKey(Constants.LAST_DOOR_REFR_KEY);
|
|
bool fatto = await RedHashRemove(currKey, doorId);
|
|
return fatto;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get Door (last) Refresh status
|
|
/// </summary>
|
|
/// <returns>Dictionary of DoorId, saveVersNumb</returns>
|
|
public async Task<Dictionary<string, string>> DoorRefreshStatus()
|
|
{
|
|
long numReq = 0;
|
|
Dictionary<string, string> dictResult = new Dictionary<string, string>();
|
|
// cerco da cache
|
|
RedisKey currKey = new RedisKey(Constants.LAST_DOOR_REFR_KEY);
|
|
Stopwatch sw = new Stopwatch();
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Start();
|
|
}
|
|
numReq = redisDb.HashLength(currKey);
|
|
if (numReq > 0)
|
|
{
|
|
var rawData = await redisDb.HashGetAllAsync(currKey);
|
|
foreach (var item in rawData)
|
|
{
|
|
dictResult.Add($"{item.Name}", $"{item.Value}");
|
|
}
|
|
}
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Stop();
|
|
// gestione statistiche
|
|
await ProcStatLog("DoorRefreshStatus", sw.Elapsed, 1, 0.3);
|
|
}
|
|
return dictResult;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Upsert info ultimo update di una porta
|
|
/// </summary>
|
|
/// <returns>Dictionary of DoorId, saveVersNumb</returns>
|
|
public async Task<long> DoorRefreshUpsert(string doorId)
|
|
{
|
|
DateTime adesso = DateTime.Now;
|
|
string sAdesso = JsonConvert.SerializeObject(adesso, JSSettings);
|
|
RedisKey currKey = new RedisKey(Constants.LAST_DOOR_REFR_KEY);
|
|
long numReq = await RedHashUpsert(currKey, doorId, sAdesso);
|
|
return numReq;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Restitusice l'SVG della porta in oggetto
|
|
/// </summary>
|
|
/// <param name="doorIdVers">formato DoorId:Versione</param>
|
|
/// <returns></returns>
|
|
public async Task<string> DoorSvgGet(string doorIdVers)
|
|
{
|
|
RedisKey currSvgKey = new RedisKey($"{Constants.CALC_REQ_SVG_CACHE}:{doorIdVers}");
|
|
RedisValue svgVal = await redisDb.StringGetAsync(currSvgKey);
|
|
return svgVal.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if specified DoorId is in Template door list
|
|
/// </summary>
|
|
/// <param name="DoorId">Door Id to search</param>
|
|
/// <returns>Found (true/false)</returns>
|
|
public async Task<bool> DoorTplListContainsDoor(string DoorId)
|
|
{
|
|
bool found = false;
|
|
RedisKey currKey = new RedisKey(Constants.DOOR_TPL_LIST);
|
|
var rawVal = await RedHashGetString(currKey, DoorId, true);
|
|
found = !string.IsNullOrEmpty(rawVal) && (rawVal == DoorId);
|
|
return found;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add specified DoorIdList to Template door list
|
|
/// </summary>
|
|
/// <param name="DoorIdList">List to add</param>
|
|
/// <returns>Num of items in list</returns>
|
|
public async Task<long> DoorTplListUpsert(List<string> DoorIdList)
|
|
{
|
|
RedisKey currKey = new RedisKey(Constants.DOOR_TPL_LIST);
|
|
long numReq = 0;
|
|
foreach (var item in DoorIdList)
|
|
{
|
|
numReq = await RedHashUpsert(currKey, item, item);
|
|
}
|
|
return numReq;
|
|
}
|
|
|
|
public async Task<bool> FlushRedisCache()
|
|
{
|
|
await Task.Delay(1);
|
|
RedisValue pattern = new RedisValue($"{Constants.BASE_HASH}:Cache*");
|
|
bool answ = await ExecFlushRedisPattern(pattern);
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get # of calculation request done
|
|
/// </summary>
|
|
public async Task<long> NumRequestDone()
|
|
{
|
|
long numReq = 0;
|
|
Dictionary<string, string> dictResult = new Dictionary<string, string>();
|
|
// cerco da cache
|
|
RedisKey currKey = new RedisKey(Constants.CALC_REQ_DONE);
|
|
Stopwatch sw = new Stopwatch();
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Start();
|
|
}
|
|
numReq = await redisDb.HashLengthAsync(currKey);
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Stop();
|
|
// gestione statistiche
|
|
await ProcStatLog("NumRequestDone", sw.Elapsed, 2, 10);
|
|
}
|
|
return numReq;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get # of calculation request with errors
|
|
/// </summary>
|
|
public async Task<long> NumRequestErrors()
|
|
{
|
|
long numReq = 0;
|
|
Dictionary<string, string> dictResult = new Dictionary<string, string>();
|
|
// cerco da cache
|
|
RedisKey currKey = new RedisKey(Constants.CALC_REQ_ERRS);
|
|
Stopwatch sw = new Stopwatch();
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Start();
|
|
}
|
|
numReq = await redisDb.HashLengthAsync(currKey);
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Stop();
|
|
// gestione statistiche
|
|
await ProcStatLog("NumRequestErrors", sw.Elapsed, 2, 10);
|
|
}
|
|
return numReq;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get # of calculation request pending
|
|
/// </summary>
|
|
public async Task<long> NumRequestPending()
|
|
{
|
|
long numReq = 0;
|
|
Dictionary<string, string> dictResult = new Dictionary<string, string>();
|
|
// cerco da cache
|
|
RedisKey currKey = new RedisKey(Constants.CALC_REQ_PEND);
|
|
Stopwatch sw = new Stopwatch();
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Start();
|
|
}
|
|
numReq = await redisDb.HashLengthAsync(currKey);
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Stop();
|
|
// gestione statistiche
|
|
await ProcStatLog("NumRequestPending", sw.Elapsed, 2, 10);
|
|
}
|
|
return numReq;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get # of calculation request processing
|
|
/// </summary>
|
|
public async Task<long> NumRequestProcessing()
|
|
{
|
|
long numReq = 0;
|
|
Dictionary<string, string> dictResult = new Dictionary<string, string>();
|
|
// cerco da cache
|
|
RedisKey currKey = new RedisKey(Constants.CALC_REQ_PROC);
|
|
Stopwatch sw = new Stopwatch();
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Start();
|
|
}
|
|
numReq = await redisDb.HashLengthAsync(currKey);
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Stop();
|
|
// gestione statistiche
|
|
await ProcStatLog("NumRequestProcessing", sw.Elapsed, 2, 10);
|
|
}
|
|
return numReq;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get single hash record
|
|
/// </summary>
|
|
/// <param name="currKey">Redis Key for Hashlist</param>
|
|
/// <param name="chiave">Requested key on list</param>
|
|
/// <returns>Value as Int</returns>
|
|
public async Task<int> RedHashGetInt(RedisKey currKey, string chiave)
|
|
{
|
|
int result = 0;
|
|
Stopwatch sw = new Stopwatch();
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Start();
|
|
}
|
|
var hasVal = await redisDb.HashExistsAsync(currKey, chiave);
|
|
if (hasVal)
|
|
{
|
|
var rawRes = await redisDb.HashGetAsync(currKey, chiave);
|
|
if (rawRes.HasValue)
|
|
{
|
|
int.TryParse($"{rawRes}", out result);
|
|
}
|
|
}
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Stop();
|
|
// gestione statistiche
|
|
await ProcStatLog("RedHashGetInt", sw.Elapsed, 0, 0.3);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get single hash record
|
|
/// </summary>
|
|
/// <param name="currKey">Redis Key for Hashlist</param>
|
|
/// <param name="chiave">Requested key on list</param>
|
|
/// <param name="doTimeLog">Execute time log processing</param>
|
|
/// <returns>Value as string</returns>
|
|
public async Task<string> RedHashGetString(RedisKey currKey, string chiave, bool doTimeLog)
|
|
{
|
|
string result = "";
|
|
Stopwatch sw = new Stopwatch();
|
|
if (logTimingEnable && doTimeLog)
|
|
{
|
|
sw.Start();
|
|
}
|
|
var hasVal = await redisDb.HashExistsAsync(currKey, chiave);
|
|
if (hasVal)
|
|
{
|
|
var rawRes = await redisDb.HashGetAsync(currKey, chiave);
|
|
if (rawRes.HasValue)
|
|
{
|
|
result = $"{rawRes}";
|
|
}
|
|
}
|
|
if (logTimingEnable && doTimeLog)
|
|
{
|
|
sw.Stop();
|
|
// gestione statistiche
|
|
await ProcStatLog("RedHashGetString", sw.Elapsed, 0, 0.3);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove for single hash record
|
|
/// </summary>
|
|
/// <param name="currKey">Chiave redis della Hashlist</param>
|
|
/// <param name="chiave">Chiave nella HashList</param>
|
|
/// <returns>Esito rimozione</returns>
|
|
public async Task<bool> RedHashRemove(RedisKey currKey, string chiave)
|
|
{
|
|
bool fatto = false;
|
|
Stopwatch sw = new Stopwatch();
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Start();
|
|
}
|
|
fatto = await redisDb.HashDeleteAsync(currKey, chiave);
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Stop();
|
|
// gestione statistiche
|
|
await ProcStatLog("RedHashRemove", sw.Elapsed, 0, 0.3);
|
|
}
|
|
return fatto;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua upsert in HasList redis
|
|
/// </summary>
|
|
/// <param name="currKey">Chiave redis della Hashlist</param>
|
|
/// <param name="chiave">Chiave nella HashList</param>
|
|
/// <param name="valore">Valore da salvare</param>
|
|
/// <param name="doTimeLog">Gestione log time abilitata</param>
|
|
/// <returns>Num record nella HashList</returns>
|
|
public async Task<long> RedHashUpsert(RedisKey currKey, string chiave, string valore, bool doTimeLog = true)
|
|
{
|
|
long numReq = 0;
|
|
Stopwatch sw = new Stopwatch();
|
|
if (logTimingEnable && doTimeLog)
|
|
{
|
|
sw.Start();
|
|
}
|
|
await redisDb.HashSetAsync(currKey, chiave, valore);
|
|
numReq = await redisDb.HashLengthAsync(currKey);
|
|
if (logTimingEnable && doTimeLog)
|
|
{
|
|
sw.Stop();
|
|
// gestione statistiche
|
|
await ProcStatLog("RedHashUpsert", sw.Elapsed, 0, 0.3);
|
|
}
|
|
return numReq;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rimuove info della porta in oggetto
|
|
/// </summary>
|
|
/// <param name="doorIdVers">formato DoorId:Versione</param>
|
|
/// <returns></returns>
|
|
public async Task<bool> RedisBulkDelHashByKey(int doorId)
|
|
{
|
|
await DoorRefreshRemove($"{doorId}");
|
|
await RequestDoneRemove($"{doorId}");
|
|
await RequestErrRemove($"{doorId}");
|
|
await RequestTypeRemove($"{doorId}");
|
|
await RequestPendingRemove($"{doorId}");
|
|
await RequestProcessingRemove($"{doorId}");
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get Queue request done
|
|
/// </summary>
|
|
/// <returns>Dictionary of DoorId, saveVersNumb</returns>
|
|
public async Task<Dictionary<string, string>> RequestDone()
|
|
{
|
|
long numReq = 0;
|
|
Dictionary<string, string> dictResult = new Dictionary<string, string>();
|
|
// cerco da cache
|
|
RedisKey currKey = new RedisKey(Constants.CALC_REQ_DONE);
|
|
Stopwatch sw = new Stopwatch();
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Start();
|
|
}
|
|
numReq = redisDb.HashLength(currKey);
|
|
if (numReq > 0)
|
|
{
|
|
var rawData = await redisDb.HashGetAllAsync(currKey);
|
|
foreach (var item in rawData)
|
|
{
|
|
dictResult.Add($"{item.Name}", $"{item.Value}");
|
|
}
|
|
}
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Stop();
|
|
// gestione statistiche
|
|
await ProcStatLog("RequestDone", sw.Elapsed, 1, 10);
|
|
}
|
|
return dictResult;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get LAST request done for DoorId
|
|
/// </summary>
|
|
/// <param name="DoorId"></param>
|
|
/// <returns></returns>
|
|
public async Task<Dictionary<string, string>> RequestDoneGetSingle(int DoorId)
|
|
{
|
|
long numReq = 0;
|
|
Dictionary<string, string> dictResult = new Dictionary<string, string>();
|
|
// cerco da cache
|
|
RedisKey currKey = new RedisKey(Constants.CALC_REQ_DONE);
|
|
Stopwatch sw = new Stopwatch();
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Start();
|
|
}
|
|
numReq = redisDb.HashLength(currKey);
|
|
if (numReq > 0)
|
|
{
|
|
var rawData = await redisDb.HashGetAsync(currKey, $"{DoorId}");
|
|
if (rawData.HasValue)
|
|
{
|
|
dictResult.Add($"{DoorId}", $"{rawData}");
|
|
}
|
|
}
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Stop();
|
|
// gestione statistiche
|
|
await ProcStatLog("RequestDoneGetSingle", sw.Elapsed, 1, 0.3);
|
|
}
|
|
return dictResult;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove for single hash record
|
|
/// </summary>
|
|
/// <returns>Dictionary of DoorId, saveVersNumb</returns>
|
|
public async Task<bool> RequestDoneRemove(string doorId)
|
|
{
|
|
RedisKey currKey = new RedisKey(Constants.CALC_REQ_DONE);
|
|
bool fatto = await RedHashRemove(currKey, doorId);
|
|
return fatto;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Upsert for single hash record
|
|
/// </summary>
|
|
/// <returns>Dictionary of DoorId, saveVersNumb</returns>
|
|
public async Task<long> RequestDoneUpsert(string doorId, string vers)
|
|
{
|
|
RedisKey currKey = new RedisKey(Constants.CALC_REQ_DONE);
|
|
long numReq = await RedHashUpsert(currKey, doorId, vers);
|
|
return numReq;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get Queue request with errors
|
|
/// </summary>
|
|
/// <returns>Dictionary of DoorId, saveVersNumb</returns>
|
|
public async Task<Dictionary<string, string>> RequestErr()
|
|
{
|
|
long numReq = 0;
|
|
Dictionary<string, string> dictResult = new Dictionary<string, string>();
|
|
// cerco da cache
|
|
RedisKey currKey = new RedisKey(Constants.CALC_REQ_ERRS);
|
|
Stopwatch sw = new Stopwatch();
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Start();
|
|
}
|
|
numReq = redisDb.HashLength(currKey);
|
|
if (numReq > 0)
|
|
{
|
|
var rawData = await redisDb.HashGetAllAsync(currKey);
|
|
foreach (var item in rawData)
|
|
{
|
|
dictResult.Add($"{item.Name}", $"{item.Value}");
|
|
}
|
|
}
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Stop();
|
|
// gestione statistiche
|
|
await ProcStatLog("RequestErr", sw.Elapsed, 1, 10);
|
|
}
|
|
return dictResult;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rimuove hash record errori
|
|
/// </summary>
|
|
/// <returns>Dictionary of DoorId, saveVersNumb</returns>
|
|
public async Task<bool> RequestErrRemove(string doorId)
|
|
{
|
|
RedisKey currKey = new RedisKey(Constants.CALC_REQ_ERRS);
|
|
bool fatto = await RedHashRemove(currKey, doorId);
|
|
return fatto;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Upsert record errori
|
|
/// </summary>
|
|
/// <returns>Dictionary of DoorId, saveVersNumb</returns>
|
|
public async Task<long> RequestErrUpsert(string doorId, string vers)
|
|
{
|
|
RedisKey currKey = new RedisKey(Constants.CALC_REQ_ERRS);
|
|
long numReq = await RedHashUpsert(currKey, doorId, vers);
|
|
return numReq;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get Queue request pending
|
|
/// </summary>
|
|
/// <returns>Dictionary of DoorId, saveVersNumb</returns>
|
|
public async Task<Dictionary<string, string>> RequestPending()
|
|
{
|
|
long numReq = 0;
|
|
Dictionary<string, string> dictResult = new Dictionary<string, string>();
|
|
// cerco da cache
|
|
RedisKey currKey = new RedisKey(Constants.CALC_REQ_PEND);
|
|
Stopwatch sw = new Stopwatch();
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Start();
|
|
}
|
|
numReq = redisDb.HashLength(currKey);
|
|
if (numReq > 0)
|
|
{
|
|
var rawData = await redisDb.HashGetAllAsync(currKey);
|
|
foreach (var item in rawData)
|
|
{
|
|
dictResult.Add($"{item.Name}", $"{item.Value}");
|
|
}
|
|
}
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Stop();
|
|
// gestione statistiche
|
|
await ProcStatLog("RequestPending", sw.Elapsed, 1, 10);
|
|
}
|
|
return dictResult;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove for single hash record
|
|
/// </summary>
|
|
/// <returns>Dictionary of DoorId, saveVersNumb</returns>
|
|
public async Task<bool> RequestPendingRemove(string doorId)
|
|
{
|
|
RedisKey currKey = new RedisKey(Constants.CALC_REQ_PEND);
|
|
bool fatto = await RedHashRemove(currKey, doorId);
|
|
return fatto;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Upsert for single hash record of req pending
|
|
/// </summary>
|
|
/// <returns>Dictionary of DoorId, saveVersNumb</returns>
|
|
public async Task<long> RequestPendingUpsert(string doorId, string vers)
|
|
{
|
|
RedisKey currKey = new RedisKey(Constants.CALC_REQ_PEND);
|
|
long numReq = await RedHashUpsert(currKey, doorId, vers);
|
|
return numReq;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get Queue request processing
|
|
/// </summary>
|
|
/// <returns>Dictionary of DoorId, saveVersNumb</returns>
|
|
public async Task<Dictionary<string, string>> RequestProcessing()
|
|
{
|
|
long numReq = 0;
|
|
Dictionary<string, string> dictResult = new Dictionary<string, string>();
|
|
// cerco da cache
|
|
RedisKey currKey = new RedisKey(Constants.CALC_REQ_PROC);
|
|
Stopwatch sw = new Stopwatch();
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Start();
|
|
}
|
|
numReq = redisDb.HashLength(currKey);
|
|
if (numReq > 0)
|
|
{
|
|
var rawData = await redisDb.HashGetAllAsync(currKey);
|
|
foreach (var item in rawData)
|
|
{
|
|
dictResult.Add($"{item.Name}", $"{item.Value}");
|
|
}
|
|
}
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Stop();
|
|
// gestione statistiche
|
|
await ProcStatLog("RequestProcessing", sw.Elapsed, 1, 10);
|
|
}
|
|
return dictResult;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove for single hash record
|
|
/// </summary>
|
|
/// <returns>Dictionary of DoorId, saveVersNumb</returns>
|
|
public async Task<bool> RequestProcessingRemove(string doorId)
|
|
{
|
|
RedisKey currKey = new RedisKey(Constants.CALC_REQ_PROC);
|
|
bool fatto = await RedHashRemove(currKey, doorId);
|
|
return fatto;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Upsert for single hash record in processing
|
|
/// </summary>
|
|
/// <returns>Dictionary of DoorId, saveVersNumb</returns>
|
|
public async Task<long> RequestProcessingUpsert(string doorId, string vers)
|
|
{
|
|
RedisKey currKey = new RedisKey(Constants.CALC_REQ_PROC);
|
|
long numReq = await RedHashUpsert(currKey, doorId, vers);
|
|
// aggiungo cache veto rimozione da coda processing
|
|
RedisKey currVetoKey = new RedisKey($"{Constants.CALC_REQ_PROC}:{doorId}");
|
|
await redisDb.StringSetAsync(currVetoKey, vers, TimeSpan.FromSeconds(vetoRemoveProcSec));
|
|
return numReq;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get list of request type
|
|
/// </summary>
|
|
/// <returns>Dictionary of DoorId, saveVersNumb</returns>
|
|
public async Task<Dictionary<string, string>> RequestType()
|
|
{
|
|
long numReq = 0;
|
|
Dictionary<string, string> dictResult = new Dictionary<string, string>();
|
|
// cerco da cache
|
|
RedisKey currKey = new RedisKey(Constants.CALC_REQ_TYPE);
|
|
Stopwatch sw = new Stopwatch();
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Start();
|
|
}
|
|
numReq = redisDb.HashLength(currKey);
|
|
if (numReq > 0)
|
|
{
|
|
var rawData = await redisDb.HashGetAllAsync(currKey);
|
|
foreach (var item in rawData)
|
|
{
|
|
dictResult.Add($"{item.Name}", $"{item.Value}");
|
|
}
|
|
}
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Stop();
|
|
// gestione statistiche
|
|
await ProcStatLog("RequestType", sw.Elapsed, 1, 10);
|
|
}
|
|
return dictResult;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove for single hash record from TYPE hash table
|
|
/// </summary>
|
|
/// <returns>Dictionary of DoorId, saveVersNumb</returns>
|
|
public async Task<bool> RequestTypeRemove(string doorId)
|
|
{
|
|
RedisKey currKey = new RedisKey(Constants.CALC_REQ_TYPE);
|
|
bool fatto = await RedHashRemove(currKey, doorId);
|
|
return fatto;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Upsert for single hash record of TYPE
|
|
/// </summary>
|
|
/// <returns>Dictionary of DoorId, Type proc requested</returns>
|
|
public async Task<bool> RequestTypeUpsert(string doorId, string type)
|
|
{
|
|
RedisKey currKey = new RedisKey(Constants.CALC_REQ_TYPE);
|
|
long numReq = 0;
|
|
bool answ = false;
|
|
if (!string.IsNullOrEmpty(type))
|
|
{
|
|
numReq = await RedHashUpsert(currKey, doorId, type);
|
|
answ = numReq > 0;
|
|
}
|
|
// se vuoto --> rimuovo!
|
|
else
|
|
{
|
|
await RedHashRemove(currKey, doorId);
|
|
answ = true;
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reset to queue request all processing/processed data
|
|
/// </summary>
|
|
/// <returns>Dictionary of DoorId, saveVersNumb</returns>
|
|
public async Task<bool> ResetQueue()
|
|
{
|
|
bool fatto = false;
|
|
Stopwatch sw = new Stopwatch();
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Start();
|
|
}
|
|
// cerco le richieste processing
|
|
RedisKey currKey = new RedisKey(Constants.CALC_REQ_PROC);
|
|
var rawData = await redisDb.HashGetAllAsync(currKey);
|
|
foreach (var item in rawData)
|
|
{
|
|
await RequestPendingUpsert(item.Name!, item.Value!);
|
|
await RequestProcessingRemove(item.Name!);
|
|
fatto = true;
|
|
}
|
|
// cerco le richieste con errori
|
|
currKey = new RedisKey(Constants.CALC_REQ_ERRS);
|
|
rawData = await redisDb.HashGetAllAsync(currKey);
|
|
foreach (var item in rawData)
|
|
{
|
|
await RequestPendingUpsert(item.Name!, item.Value!);
|
|
await RequestErrRemove(item.Name!);
|
|
fatto = true;
|
|
}
|
|
// cerco le richieste processed
|
|
currKey = new RedisKey(Constants.CALC_REQ_DONE);
|
|
rawData = await redisDb.HashGetAllAsync(currKey);
|
|
foreach (var item in rawData)
|
|
{
|
|
await RequestPendingUpsert(item.Name!, item.Value!);
|
|
await RequestDoneRemove(item.Name!);
|
|
fatto = true;
|
|
}
|
|
// svuoto in blocco hashatble tipo richieste...
|
|
currKey = new RedisKey(Constants.CALC_REQ_TYPE);
|
|
rawData = await redisDb.HashGetAllAsync(currKey);
|
|
foreach (var item in rawData)
|
|
{
|
|
await RequestTypeRemove(item.Name!);
|
|
fatto = true;
|
|
}
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Stop();
|
|
// gestione statistiche
|
|
await ProcStatLog("ResetQueue", sw.Elapsed, 1, 0.3);
|
|
}
|
|
return fatto;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reset to queue request for specified DoorIdList
|
|
/// </summary>
|
|
/// <returns>Dictionary of DoorId, saveVersNumb</returns>
|
|
public async Task<List<string>> ResetQueueByDoorList(List<string> DoorIdList)
|
|
{
|
|
int num2proc = DoorIdList.Count;
|
|
List<string> listDone = new List<string>();
|
|
Stopwatch sw = new Stopwatch();
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Start();
|
|
}
|
|
// cerco le richieste processing
|
|
RedisKey currKey = new RedisKey(Constants.CALC_REQ_PROC);
|
|
var rawData = await redisDb.HashGetAllAsync(currKey);
|
|
foreach (var item in rawData)
|
|
{
|
|
// se è nell'elenco...
|
|
if (DoorIdList.Contains($"{item.Name}"))
|
|
{
|
|
await RequestPendingUpsert(item.Name!, item.Value!);
|
|
await RequestProcessingRemove(item.Name!);
|
|
listDone.Add($"{item.Name}");
|
|
}
|
|
}
|
|
// cerco le richieste con errori
|
|
currKey = new RedisKey(Constants.CALC_REQ_ERRS);
|
|
rawData = await redisDb.HashGetAllAsync(currKey);
|
|
foreach (var item in rawData)
|
|
{
|
|
// se è nell'elenco...
|
|
if (DoorIdList.Contains($"{item.Name}"))
|
|
{
|
|
await RequestPendingUpsert(item.Name!, item.Value!);
|
|
await RequestErrRemove(item.Name!);
|
|
listDone.Add($"{item.Name}");
|
|
}
|
|
}
|
|
// cerco le richieste processed
|
|
currKey = new RedisKey(Constants.CALC_REQ_DONE);
|
|
rawData = await redisDb.HashGetAllAsync(currKey);
|
|
foreach (var item in rawData)
|
|
{
|
|
// se è nell'elenco...
|
|
if (DoorIdList.Contains($"{item.Name}"))
|
|
{
|
|
await RequestPendingUpsert(item.Name!, item.Value!);
|
|
await RequestDoneRemove(item.Name!);
|
|
listDone.Add($"{item.Name}");
|
|
}
|
|
}
|
|
// svuoto hashatble tipo richieste...
|
|
currKey = new RedisKey(Constants.CALC_REQ_TYPE);
|
|
rawData = await redisDb.HashGetAllAsync(currKey);
|
|
foreach (var item in rawData)
|
|
{
|
|
// se è nell'elenco...
|
|
if (DoorIdList.Contains($"{item.Name}"))
|
|
{
|
|
await RequestTypeRemove(item.Name!);
|
|
listDone.Add($"{item.Name}");
|
|
}
|
|
}
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Stop();
|
|
Log.Debug($"ResetQueueByDoorList | doors #: {num2proc} --> {DoorIdList.Count} | EXEC in: {sw.Elapsed.TotalMilliseconds} ms");
|
|
}
|
|
return DoorIdList;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reset to queue request all processing stuck data (veto check)
|
|
/// </summary>
|
|
/// <returns>Boolean, executed</returns>
|
|
public async Task<bool> ResetQueueProcessing()
|
|
{
|
|
bool fatto = false;
|
|
await Task.Delay(1);
|
|
Stopwatch sw = new Stopwatch();
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Start();
|
|
}
|
|
// cerco le richieste processing
|
|
RedisKey currKey = new RedisKey(Constants.CALC_REQ_PROC);
|
|
RedisKey currVetoKey = new RedisKey($"{Constants.CALC_REQ_PROC}:0");
|
|
var rawData = await redisDb.HashGetAllAsync(currKey);
|
|
foreach (var item in rawData)
|
|
{
|
|
// verifico SE siano senza veto rimozione...
|
|
currVetoKey = new RedisKey($"{Constants.CALC_REQ_PROC}:{item.Name}");
|
|
var redisVal = await redisDb.StringGetAsync(currVetoKey);
|
|
if (!redisVal.HasValue)
|
|
{
|
|
await RequestPendingUpsert(item.Name!, item.Value!);
|
|
await RequestProcessingRemove(item.Name!);
|
|
fatto = true;
|
|
}
|
|
}
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Stop();
|
|
// gestione statistiche
|
|
await ProcStatLog("ResetQueueProcessing", sw.Elapsed, 1, 10);
|
|
}
|
|
return fatto;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Salvo elenco risultati elaborazioni (modalità boolean di esecuzione corretta)
|
|
/// </summary>
|
|
/// <param name="calcResults">Risultati elaborazioni in formato CalcResultDTO</param>
|
|
/// <returns></returns>
|
|
public async Task<bool> SaveProcessingResult(List<CalcResultDTO> calcResults)
|
|
{
|
|
bool answ = true;
|
|
if (calcResults != null && calcResults.Count > 0)
|
|
{
|
|
string sDoorId = "";
|
|
string sCurrVers = "";
|
|
bool doorIsTpl = false;
|
|
foreach (var calcTask in calcResults)
|
|
{
|
|
RedisKey currSvgKey = new RedisKey("");
|
|
var doorData = calcTask.DoorIdVers.Split(".");
|
|
sDoorId = doorData.Length > 0 ? doorData[0] : "";
|
|
sCurrVers = doorData.Length > 0 ? doorData[1] : "";
|
|
// verifico se sia template --> durata indefinita...
|
|
doorIsTpl = await DoorTplListContainsDoor(sDoorId);
|
|
// se valido salvo SVG...
|
|
if (calcTask.Validated)
|
|
{
|
|
// salvo in area REDIS
|
|
currSvgKey = new RedisKey($"{Constants.CALC_REQ_SVG_CACHE}:{sDoorId}:{sCurrVers}");
|
|
// se template --> no scadenza
|
|
if (doorIsTpl)
|
|
{
|
|
await redisDb.StringSetAsync(currSvgKey, calcTask.RawContent);
|
|
}
|
|
else
|
|
{
|
|
// 2024.02.15 salvo in cache x 2 settimane, fare 1+ mesi?!?
|
|
await redisDb.StringSetAsync(currSvgKey, calcTask.RawContent, DblWeekLongCache);
|
|
}
|
|
// elimino dalle code proc/err
|
|
await RequestProcessingRemove(sDoorId);
|
|
await RequestErrRemove(sDoorId);
|
|
// metto in coda done
|
|
await RequestDoneUpsert(sDoorId, sCurrVers);
|
|
}
|
|
// altrimenti salvo errore e metto in coda errori
|
|
else
|
|
{
|
|
// salvo in area REDIS
|
|
currSvgKey = new RedisKey($"{Constants.CALC_REQ_ERRS}:{sDoorId}:{sCurrVers}");
|
|
if (doorIsTpl)
|
|
{
|
|
await redisDb.StringSetAsync(currSvgKey, calcTask.ErrorMsg);
|
|
}
|
|
else
|
|
{
|
|
await redisDb.StringSetAsync(currSvgKey, calcTask.ErrorMsg, WeekLongCache);
|
|
}
|
|
// elimino dalle code proc/done
|
|
await RequestProcessingRemove(sDoorId);
|
|
await RequestDoneRemove(sDoorId);
|
|
// metto in coda err
|
|
await RequestErrUpsert(sDoorId, sCurrVers);
|
|
}
|
|
// invio il messaggio di ritorno...
|
|
CalcDonePipe.saveAndSendMessage(Constants.LAST_CALC_DONE_KEY, calcTask.DoorIdVers);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
await Task.Delay(1);
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invio richiesta di calcolo per la porta indicata, dato il suo DDF
|
|
/// </summary>
|
|
/// <param name="DoorId"></param>
|
|
/// <param name="FullDDF">Contenuto completo del DDF</param>
|
|
/// <param name="MimeType">Tipo di file richeisto in OUT: svg / 3dm...</param>
|
|
/// <returns>indice/versione del calcolo DDF</returns>
|
|
public async Task<int> SendCalcReq(int DoorId, string FullDDF, string? MimeType = "")
|
|
{
|
|
Stopwatch sw = new Stopwatch();
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Start();
|
|
}
|
|
string sDoorId = $"{DoorId}";
|
|
int currVers = await DoorCalcRev(sDoorId);
|
|
string sCurrVers = $"{currVers}";
|
|
// salvo il mimeType (se nullo è "svg")
|
|
string mimeType = MimeType ?? "";
|
|
await RequestTypeUpsert(sDoorId, mimeType);
|
|
// elimino da code errori (SE fosse presente)
|
|
await RequestErrRemove(sDoorId);
|
|
|
|
// salvo nell'archivio REDIS delle porte il DDF corrente (key=doorId.versNumb), potrebbe
|
|
// venire buono anche x eventuale UNDO...
|
|
RedisKey currDdfKey = new RedisKey($"{Constants.CALC_REQ_DDF_CACHE}:{sDoorId}:{sCurrVers}");
|
|
await redisDb.StringSetAsync(currDdfKey, FullDDF, DblWeekLongCache);
|
|
|
|
// invio sul canale dei messaggi il numero di items in coda attuali x chiedere esecuzione...
|
|
long numPending = await NumRequestPending();
|
|
bool answ = CalcReqPipe.saveAndSendMessage(Constants.LAST_CALC_REQ_KEY, $"{numPending}");
|
|
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Stop();
|
|
Log.Debug($"SendCalcReq | DoorId: {DoorId} enqueued in: {sw.Elapsed.TotalMilliseconds} ms");
|
|
}
|
|
return currVers;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Restituisce dati statistica richiesta
|
|
/// </summary>
|
|
/// <param name="statName">Nome statistica</param>
|
|
/// <returns></returns>
|
|
public async Task<ExecStats> StatGetAsync(string statName)
|
|
{
|
|
ExecStats answ = new ExecStats(0, new TimeSpan());
|
|
RedisKey currKey = new RedisKey(Constants.STATS_DATA);
|
|
// cerco nella hashTable...
|
|
var rawData = await RedHashGetString(currKey, statName, false);
|
|
// se c'è rileggo ed aggiorno
|
|
if (!string.IsNullOrEmpty(rawData))
|
|
{
|
|
var currStat = JsonConvert.DeserializeObject<ExecStats>(rawData);
|
|
if (currStat != null)
|
|
{
|
|
answ = currStat;
|
|
}
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resetta dati statistica esecuzione x log restituendo i valori precedentemente contenuti
|
|
/// </summary>
|
|
/// <param name="statName">Nome statistica</param>
|
|
/// <returns></returns>
|
|
public async Task<ExecStats> StatReset(string statName)
|
|
{
|
|
RedisKey currKey = new RedisKey(Constants.STATS_DATA);
|
|
ExecStats answ = await StatGetAsync(statName);
|
|
// ora rimuovo valore
|
|
await RedHashRemove(currKey, statName);
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua upsert dati statistica esecuzione x log
|
|
/// </summary>
|
|
/// <param name="statName">Nome statistica</param>
|
|
/// <param name="duration">Durata esecuzione come timespan</param>
|
|
/// <param name="evMultipl">moltiplicatore soglia x log statistica (default = 1)</param>
|
|
/// <returns></returns>
|
|
public async Task<bool> StatUpsert(string statName, TimeSpan duration, double evMultipl)
|
|
{
|
|
bool answ = false;
|
|
RedisKey currKey = new RedisKey(Constants.STATS_DATA);
|
|
// cerco nella hashTable...
|
|
var rawData = await RedHashGetString(currKey, statName, false);
|
|
// di default inizializzo a null...
|
|
ExecStats newStat = new ExecStats(1, duration);
|
|
// se c'è rileggo ed aggiorno
|
|
if (!string.IsNullOrEmpty(rawData))
|
|
{
|
|
var currStat = JsonConvert.DeserializeObject<ExecStats>(rawData);
|
|
if (currStat != null)
|
|
{
|
|
currStat.NumCall++;
|
|
currStat.TotalTime += duration;
|
|
newStat = currStat;
|
|
}
|
|
}
|
|
// ora aggiorno su redis
|
|
rawData = JsonConvert.SerializeObject(newStat);
|
|
var numRec = await RedHashUpsert(currKey, statName, rawData, false);
|
|
// verifico se da considerare superato limite sample
|
|
answ = newStat.NumCall >= (int)(statSampleSize * evMultipl);
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get Queue request pending, removing from queue and putting on processing queue
|
|
/// </summary>
|
|
/// <returns>Dictionary of DoorId, saveVersNumb</returns>
|
|
public async Task<Dictionary<string, CalcReqtDTO>> TakeProcessingItems(int numItems)
|
|
{
|
|
int maxTake = Math.Min(10, numItems);
|
|
long numReq = 0;
|
|
Dictionary<string, CalcReqtDTO> dictResult = new Dictionary<string, CalcReqtDTO>();
|
|
// cerco da cache
|
|
RedisKey currKey = new RedisKey(Constants.CALC_REQ_PEND);
|
|
Stopwatch sw = new Stopwatch();
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Start();
|
|
}
|
|
// calcolo il totale delle richieste pending
|
|
numReq = redisDb.HashLength(currKey);
|
|
if (numReq > 0)
|
|
{
|
|
// per prima cosa aspetto che NON sia locked la coda...
|
|
while (queueLock)
|
|
{
|
|
Thread.Sleep(rnd.Next(1, 20));
|
|
}
|
|
// blocco
|
|
queueLock = true;
|
|
var rawData = await redisDb.HashGetAllAsync(currKey);
|
|
// sposto fino a concorrenza...
|
|
foreach (var item in rawData)
|
|
{
|
|
// per prima cosa tolgo da item richiesti e metto in processing
|
|
await RequestPendingRemove(item.Name!);
|
|
await RequestProcessingUpsert(item.Name!, item.Value!);
|
|
maxTake--;
|
|
// recupero il DDF...
|
|
RedisKey currDdfKey = new RedisKey($"{Constants.CALC_REQ_DDF_CACHE}:{item.Name}:{item.Value}");
|
|
var rawDDF = await redisDb.StringGetAsync(currDdfKey);
|
|
// verifico il tpo richiesta, se non trovassi prendo "svg" default...
|
|
var curTypeKey = new RedisKey(Constants.CALC_REQ_TYPE);
|
|
var rawType = await RedHashGetString(curTypeKey, item.Name!, true);
|
|
string mimeType = string.IsNullOrEmpty(rawType) ? "svg" : $"{rawType}";
|
|
// preparo DTO
|
|
CalcReqtDTO currReq = new CalcReqtDTO()
|
|
{
|
|
MimeType = mimeType,
|
|
DDF = $"{rawDDF}"
|
|
};
|
|
dictResult.Add($"{item.Name}.{item.Value}", currReq);
|
|
if (maxTake <= 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
// sblocco
|
|
queueLock = false;
|
|
}
|
|
if (logTimingEnable)
|
|
{
|
|
sw.Stop();
|
|
// gestione statistiche
|
|
await ProcStatLog("TakeProcessingItems", sw.Elapsed, 1, 0.3);
|
|
}
|
|
return dictResult;
|
|
}
|
|
|
|
#endregion Public Methods
|
|
|
|
#region Protected Fields
|
|
|
|
protected const string rKeyCalcOreFase = "Check:OreFasi";
|
|
|
|
protected const string rKeyFasiAct = "Check:FasiAct";
|
|
|
|
protected const string rKeyProjAct = "Check:ProjAct";
|
|
|
|
/// <summary>
|
|
/// TTL da 1 min x cache Redis
|
|
/// </summary>
|
|
protected const int shortTTL = 60 * 5;
|
|
|
|
protected int idxSim = 0;
|
|
|
|
protected Random rnd = new Random();
|
|
|
|
#endregion Protected Fields
|
|
|
|
#region Protected Properties
|
|
|
|
protected static bool queueLock { get; set; } = false;
|
|
|
|
#endregion Protected Properties
|
|
|
|
#region Protected Methods
|
|
|
|
/// <summary>
|
|
/// Recupera revisione corrente della porta o inizializza
|
|
/// </summary>
|
|
/// <param name="DoorId"></param>
|
|
/// <returns></returns>
|
|
protected async Task<int> DoorCalcRev(string sDoorId)
|
|
{
|
|
/*----------------------------------
|
|
* Recupero Rev corrente porta o inizializza cercando
|
|
* - coda calcoli pending
|
|
* - coda errori
|
|
* - coda calcoli eseguiti
|
|
*
|
|
* se non trova riparte da 1
|
|
*
|
|
----------------------------------*/
|
|
int currVers = 0;
|
|
string sCurrVers = "";
|
|
// per prima cosa controllo se ho GIA' in coda qualcosa come richieste
|
|
if (await NumRequestPending() > 0)
|
|
{
|
|
var currPending = await RequestPending();
|
|
if (currPending != null)
|
|
{
|
|
if (currPending.ContainsKey(sDoorId))
|
|
{
|
|
sCurrVers = currPending[sDoorId];
|
|
}
|
|
}
|
|
}
|
|
// cerco coda errori
|
|
if (string.IsNullOrEmpty(sCurrVers) && await NumRequestErrors() > 0)
|
|
{
|
|
var currErrors = await RequestErr();
|
|
if (currErrors != null)
|
|
{
|
|
if (currErrors.ContainsKey(sDoorId))
|
|
{
|
|
sCurrVers = currErrors[sDoorId];
|
|
}
|
|
}
|
|
}
|
|
// cerco coda task completati
|
|
if (string.IsNullOrEmpty(sCurrVers) && await NumRequestDone() > 0)
|
|
{
|
|
var currDone = await RequestDone();
|
|
if (currDone != null)
|
|
{
|
|
if (currDone.ContainsKey(sDoorId))
|
|
{
|
|
sCurrVers = currDone[sDoorId];
|
|
}
|
|
}
|
|
}
|
|
// calcolo ed incremento
|
|
int.TryParse(sCurrVers, out currVers);
|
|
currVers++;
|
|
// inserisco in coda x calcoli
|
|
await RequestPendingUpsert(sDoorId, $"{currVers}");
|
|
// metto in coda "last calc" la porta corrente x pulizia successiva
|
|
await DoorRefreshUpsert(sDoorId);
|
|
return currVers;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recupero chiave da redis
|
|
/// </summary>
|
|
/// <param name="rKey"></param>
|
|
/// <returns></returns>
|
|
protected async Task<string> getRSV(string rKey)
|
|
{
|
|
string answ = "";
|
|
var rawData = await redisDb.StringGetAsync(rKey);
|
|
if (rawData.HasValue)
|
|
{
|
|
answ = $"{rawData}";
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Salvataggio chiave in redis
|
|
/// </summary>
|
|
/// <param name="rKey"></param>
|
|
/// <param name="rVal"></param>
|
|
/// <param name="ttlSec"></param>
|
|
/// <returns></returns>
|
|
protected async Task<bool> setRSV(string rKey, string rVal, int ttlSec)
|
|
{
|
|
bool fatto = false;
|
|
fatto = await redisDb.StringSetAsync(rKey, rVal, TimeSpan.FromSeconds(ttlSec));
|
|
return fatto;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Salvataggio chiave in redis
|
|
/// </summary>
|
|
/// <param name="rKey"></param>
|
|
/// <param name="rValInt"></param>
|
|
/// <param name="ttlSec"></param>
|
|
/// <returns></returns>
|
|
protected async Task<bool> setRSV(string rKey, int rValInt, int ttlSec)
|
|
{
|
|
bool fatto = false;
|
|
fatto = await redisDb.StringSetAsync(rKey, rValInt, TimeSpan.FromSeconds(ttlSec));
|
|
return fatto;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registra in cache chiave se non fosse già in elenco
|
|
/// </summary>
|
|
/// <param name="newKey"></param>
|
|
protected void trackCache(string newKey)
|
|
{
|
|
if (!cachedDataList.Contains(newKey))
|
|
{
|
|
cachedDataList.Add(newKey);
|
|
}
|
|
}
|
|
|
|
#endregion Protected Methods
|
|
|
|
#region Private Fields
|
|
|
|
private static IConfiguration _configuration = null!;
|
|
|
|
private static JsonSerializerSettings? JSSettings;
|
|
|
|
private static Logger Log = LogManager.GetCurrentClassLogger();
|
|
|
|
/// <summary>
|
|
/// Dimensione campione da registrare e trascrivere in log
|
|
/// </summary>
|
|
private static int statSampleSize = 1;
|
|
|
|
private readonly IEmailSender _emailSender;
|
|
|
|
/// <summary>
|
|
/// Elenco obj in cache
|
|
/// </summary>
|
|
private List<string> cachedDataList = new List<string>();
|
|
|
|
/// <summary>
|
|
/// Durata cache lunga IN SECONDI
|
|
/// </summary>
|
|
private int cacheTtlLong = 60 * 5;
|
|
|
|
/// <summary>
|
|
/// Durata cache breve IN SECONDI
|
|
/// </summary>
|
|
private int cacheTtlShort = 60 * 1;
|
|
|
|
/// <summary>
|
|
/// abilitazione time log con stopwatch
|
|
/// </summary>
|
|
private bool logTimingEnable = false;
|
|
|
|
/// <summary>
|
|
/// Path dell'immagine "missing"
|
|
/// </summary>
|
|
private string missingFilePath = "";
|
|
|
|
/// <summary>
|
|
/// Contenuto SVG dell'immagine missing
|
|
/// </summary>
|
|
private string missingSvgContent = "";
|
|
|
|
/// <summary>
|
|
/// Oggetto per connessione a REDIS
|
|
/// </summary>
|
|
private ConnectionMultiplexer redisConn = null!;
|
|
|
|
/// <summary>
|
|
/// Oggetto DB redis da impiegare x chiamate R/W
|
|
/// </summary>
|
|
private IDatabase redisDb = null!;
|
|
|
|
#endregion Private Fields
|
|
|
|
#region Private Properties
|
|
|
|
/// <summary>
|
|
/// Durata cache di 24h
|
|
/// </summary>
|
|
private TimeSpan DayLongCache
|
|
{
|
|
get => TimeSpan.FromHours(24);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Durata cache di 2 settimane/15gg
|
|
/// </summary>
|
|
private TimeSpan DblWeekLongCache
|
|
{
|
|
get => TimeSpan.FromHours(24 * 15 * rnd.Next(1000, 1100) / 1000);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Durata cache lunga (+ perturbazione percentuale +/-10%)
|
|
/// </summary>
|
|
private TimeSpan FastCache
|
|
{
|
|
get => TimeSpan.FromSeconds(cacheTtlShort * rnd.Next(900, 1100) / 1000);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Durata cache lunga (+ perturbazione percentuale +/-10%)
|
|
/// </summary>
|
|
private TimeSpan LongCache
|
|
{
|
|
get => TimeSpan.FromSeconds(cacheTtlLong * rnd.Next(900, 1100) / 1000);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Durata cache di 24h
|
|
/// </summary>
|
|
private TimeSpan MonthLongCache
|
|
{
|
|
get => TimeSpan.FromDays(30);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Durata cache lunga (+ perturbazione percentuale +/-10%)
|
|
/// </summary>
|
|
private TimeSpan UltraLongCache
|
|
{
|
|
get => TimeSpan.FromSeconds(cacheTtlLong * 10 * rnd.Next(900, 1100) / 1000);
|
|
}
|
|
|
|
private int vetoRemoveProcSec { get; set; } = 10;
|
|
|
|
/// <summary>
|
|
/// Durata cache di 1 settimana
|
|
/// </summary>
|
|
private TimeSpan WeekLongCache
|
|
{
|
|
get => TimeSpan.FromHours(24 * 7 * rnd.Next(1000, 1100) / 1000);
|
|
}
|
|
|
|
#endregion Private Properties
|
|
|
|
#region Private Methods
|
|
|
|
/// <summary>
|
|
/// Esegue flush memoria redis dato pattern
|
|
/// </summary>
|
|
/// <param name="pattern"></param>
|
|
/// <returns></returns>
|
|
private async Task<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)
|
|
{
|
|
await redisDb.KeyDeleteAsync(item);
|
|
}
|
|
// brutalmente rimuovo intero contenuto DB... DANGER
|
|
//await server.FlushDatabaseAsync();
|
|
answ = true;
|
|
}
|
|
}
|
|
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processa log statistiche:
|
|
/// - accumula dati
|
|
/// - se superata soglia (soglia x evMultipl) registra log
|
|
/// </summary>
|
|
/// <param name="statName">nome statistica</param>
|
|
/// <param name="elapsed">durata evento tracciato</param>
|
|
/// <param name="logLevReq">
|
|
/// livello richiesto: 0:trace, 1:debug, 2:info, 3:warn, 4:error, 5: fatal, 6: off
|
|
/// </param>
|
|
/// <param name="evMultipl">moltiplicatore soglia x log statistica (default = 1)</param>
|
|
/// <returns></returns>
|
|
private async Task ProcStatLog(string statName, TimeSpan elapsed, int logLevReq, double evMultipl)
|
|
{
|
|
bool doWrite = await StatUpsert(statName, elapsed, evMultipl);
|
|
if (doWrite)
|
|
{
|
|
// recupero e resetto
|
|
ExecStats statRec = await StatReset(statName);
|
|
string logMsg = $"Eseguito {statName} x {statRec.NumCall} | {statRec.AvgTime.TotalMilliseconds:N3}ms";
|
|
switch (logLevReq)
|
|
{
|
|
case 0:
|
|
Log.Trace(logMsg);
|
|
break;
|
|
|
|
case 1:
|
|
Log.Debug(logMsg);
|
|
break;
|
|
|
|
case 2:
|
|
Log.Info(logMsg);
|
|
break;
|
|
|
|
case 3:
|
|
Log.Warn(logMsg);
|
|
break;
|
|
|
|
case 4:
|
|
Log.Error(logMsg);
|
|
break;
|
|
|
|
case 5:
|
|
Log.Fatal(logMsg);
|
|
break;
|
|
|
|
case 6:
|
|
default:
|
|
Log.Error(logMsg);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion Private Methods
|
|
}
|
|
} |