using EgwMultiEngineManager; using Lux.Data; using Lux.Data.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal; using Microsoft.Extensions.Configuration; using Newtonsoft.Json; using StackExchange.Redis; using System.Diagnostics; using System.Runtime; namespace Lux.API.Controllers { [Route("api/[controller]")] [ApiController] public class WindowController : ControllerBase { #region Public Constructors public WindowController(IConfiguration config, ILogger logger, ImageCacheService imgServ, IConnectionMultiplexer redisConn) //ExecProcessManager? EgwProcManager = null) { _logger = logger; _config = config; // setup REDIS da DI #if false redisConn = ConnectionMultiplexer.Connect(_config.GetConnectionString("Redis") ?? "localhost"); #endif redisDb = redisConn.GetDatabase(); _currSub = redisConn.GetSubscriber(); // 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 }; // init classe sottoscrizione PubSub CHannel messages REDIS _currSub.Subscribe(ChannelName("", true), (channel, message) => { SaveCalcData(channel, message); }); #if false // verifico se usare engine enableEgwEng = _config.GetValue("ServerConf:EgwEngineEnab"); if (enableEgwEng && EgwProcManager != null) { ProcessMan = EgwProcManager; ProcessMan.m_AnswerReceived += ProcessMan_m_AnswerReceived; } #endif ImgService = imgServ; } #if false /// /// Message Dispatcher: oggetto comunicazione pub/sub via REDIS channels corrente /// protected ISubscriber messageDisp { get { ISubscriber answ; // se già valorizzato uso oggetto private... if (_currSub != null) { answ = _currSub; } else { // sottoscrizione al dispatcher messaggi answ = redisConn.GetSubscriber(); _currSub = answ; } // restituisco oggetto DB return answ; } } #endif #endregion Public Constructors #region Public Methods /// /// Chiamata POST: riceve Json in formato JWD serializzato, lo ripete come risposta /// PUT: api/calc/svg/00000000-0000-0000-0000-000000000000 /// /// id oggetto /// [HttpPost("calc/{id}")] public async Task> calc(string id, [FromBody] string currJwd) { Stopwatch sw = new Stopwatch(); sw.Start(); lastSvg = ""; string svgContent = ""; // se messaggio vuoto --> uso default! currJwd = string.IsNullOrEmpty(currJwd) ? demoJwd : currJwd; // ...se ricevo percorso --> leggo jwd/svg cablato if (!string.IsNullOrEmpty(currJwd)) { #if false if (true || (enableEgwEng && ProcessMan != null)) #endif if (true) { Dictionary DictExec = new Dictionary(); DictExec.Add("Mode", "1"); DictExec.Add("Jwd", currJwd); int nId = 1; ProcessArgs currArgs = new ProcessArgs(nId, DictExec); sendMessage(ChannelName("", false), currArgs.sProcessArgs); svgContent = "DONE"; #if false bool done = ProcessMan.ArgumentsEnqueue(currArgs); waitResult = true; int numWait = 200; while (numWait > 0 && waitResult) { numWait--; await Task.Delay(waitDelay); } #endif } #if false // se ho risultato mostro... if (!string.IsNullOrEmpty(lastSvg)) { svgContent = lastSvg; // salvo! ImgService.SaveSvg(id, svgContent); } else { svgContent = "EMPTY"; } #endif } sw.Stop(); _logger.LogInformation($"svgString | {sw.Elapsed.TotalMilliseconds:N3} ms"); return Ok(svgContent); } /// /// Chiamata POST: riceve Json in formato JWD serializzato, lo ripete come risposta /// PUT: api/calc/svg/00000000-0000-0000-0000-000000000000 /// /// id oggetto /// [HttpGet("svg/{id}")] public async Task> svg(string id) { Stopwatch sw = new Stopwatch(); sw.Start(); string svgContent = ""; // ...se ricevo percorso --> leggo jwd/svg cablato if (!string.IsNullOrEmpty(id)) { svgContent = ImgService.LoadSvg(id); } sw.Stop(); _logger.LogInformation($"svgString | {sw.Elapsed.TotalMilliseconds:N3} ms"); return Ok(svgContent); } /// /// Chiamata POST: riceve Json in formato JWD serializzato, lo ripete come risposta /// PUT: api/calc/svg/00000000-0000-0000-0000-000000000000 /// /// id oggetto /// [HttpGet("svgfile/{id}")] public ActionResult svgFile(string id) { Stopwatch sw = new Stopwatch(); sw.Start(); if (string.IsNullOrEmpty(id)) return NotFound(); string svgContent = ImgService.LoadSvg(id); byte[] bytes = System.Text.Encoding.UTF8.GetBytes(svgContent); sw.Stop(); _logger.LogInformation($"svgString | {sw.Elapsed.TotalMilliseconds:N3} ms"); return File(bytes, "image/svg+xml"); } /// /// Chiamata POST: riceve Json in formato JWD serializzato, lo ripete come risposta /// PUT: api/calc/svg/00000000-0000-0000-0000-000000000000 /// /// id oggetto /// [HttpGet("svgfileasync/{id}")] public async Task> svgFileAsync(string id) { Stopwatch sw = new Stopwatch(); sw.Start(); if (string.IsNullOrEmpty(id)) return NotFound(); string svgContent = await ImgService.LoadSvgAsync(id); byte[] bytes = System.Text.Encoding.UTF8.GetBytes(svgContent); sw.Stop(); _logger.LogInformation($"svgString | {sw.Elapsed.TotalMilliseconds:N3} ms"); return File(bytes, "image/svg+xml"); } #endregion Public Methods #region Protected Fields /// /// conf speciale serializzatore JSON /// protected JsonSerializerSettings? JSSettings; /// /// 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 Protected Properties #endregion Protected Properties #region Private Fields private readonly ILogger _logger; private IConfiguration _config; /// /// Demorichiesta jwd x fare test richiesta calcolo /// private string demoJwd = "{\r\n\"ProfilePath\":\"Profilo78\",\r\n\"AreaList\":[\r\n{\r\n\"Shape\":\"RECTANGLE\",\r\n\"DimensionList\":[\r\n{\r\n\"nIndex\":1,\r\n\"sName\":\"Width\",\r\n\"dValue\":1200.0\r\n},\r\n{\r\n\"nIndex\":2,\r\n\"sName\":\"Height\",\r\n\"dValue\":1500.0\r\n}\r\n],\r\n\"JointList\":[\r\n{\r\n\"nIndex\":1,\r\n\"JointType\":\"FULL_V\"\r\n},\r\n{\r\n\"nIndex\":2,\r\n\"JointType\":\"FULL_V\"\r\n},\r\n{\r\n\"nIndex\":3,\r\n\"JointType\":\"FULL_V\"\r\n},\r\n{\r\n\"nIndex\":4,\r\n\"JointType\":\"FULL_V\"\r\n}\r\n],\r\n\"BottomRail\":false,\r\n\"BottomRailQty\":0,\r\n\"AreaList\":[\r\n{\r\n\"bIsSashVertical\":true,\r\n\"SashList\":[\r\n{\r\n\"OpeningType\":\"TURNONLY_LEFT\",\r\n\"bHasHandle\":false,\r\n\"dDimension\":50.0\r\n},\r\n{\r\n\"OpeningType\":\"TILTTURN_RIGHT\",\r\n\"bHasHandle\":true,\r\n\"dDimension\":50.0\r\n}\r\n],\r\n\"SashType\":\"NULL\",\r\n\"JointList\":[\r\n{\r\n\"nIndex\":1,\r\n\"JointType\":\"FULL_V\"\r\n},\r\n{\r\n\"nIndex\":2,\r\n\"JointType\":\"FULL_V\"\r\n},\r\n{\r\n\"nIndex\":3,\r\n\"JointType\":\"FULL_V\"\r\n},\r\n{\r\n\"nIndex\":4,\r\n\"JointType\":\"FULL_V\"\r\n}\r\n],\r\n\"Hardware\":\"000000\",\r\n\"AreaList\":[\r\n{\r\n\"AreaList\":[\r\n{\r\n\"FillType\":\"GLASS\",\r\n\"AreaList\":[],\r\n\"AreaType\":\"FILL\"\r\n}\r\n],\r\n\"AreaType\":\"SPLITTED\"\r\n},\r\n{\r\n\"AreaList\":[\r\n{\r\n\"FillType\":\"GLASS\",\r\n\"AreaList\":[],\r\n\"AreaType\":\"FILL\"\r\n}\r\n],\r\n\"AreaType\":\"SPLITTED\"\r\n}\r\n],\r\n\"AreaType\":\"SASH\"\r\n}\r\n],\r\n\"AreaType\":\"FRAME\"\r\n}\r\n]\r\n}"; private bool enableEgwEng = false; //private Dictionary listSvg = new Dictionary(); private string lastSvg = ""; #endregion Private Fields #region Private Properties /// /// Oggetto subscriber x pubblicazione/sottoscrizione canali REDIS /// private ISubscriber _currSub { get; set; } = null!; private ImageCacheService ImgService { get; set; } #endregion Private Properties #if false /// /// Esecutore di processi generico /// private ExecProcessManager? ProcessMan { get; set; } #endif #region Private Methods /// /// Nome del channel sottoscritto per ritorno calcoli /// /// /// private string ChannelName(string servId, bool isOut) { return isOut ? $"EgwEngineOutput" : $"EgwEngineInput"; // se implementassimo multi-elaborazione calcoli ogni esecutore ha un suo channel //return $"EgwEngineOutput_{servId}"; } /// /// Restituisce una risposta all'esecuzione /// /// private void ProcessMan_m_AnswerReceived(ProcessArgsResult result) { // verifico che sia la mia richeista con id immagine... FARE!!! // salvo il risultato... if (result.Args != null && result.Args.Count > 0) { if (result.Args.ContainsKey("Svg")) { // salvo SVG string newSvg = result.Args["Svg"]; // salvo nel dizionario lastSvg = newSvg; // salvo su redis ImgService.SaveSvg("123456", lastSvg); #if false waitResult = false; #endif } } } /// /// Salva risultato calcolo da broadcast channel REDIS /// /// /// private void SaveCalcData(RedisChannel channel, RedisValue message) { string rawData = $"{message}"; if (!string.IsNullOrEmpty(rawData) && rawData.Length > 2) { // provo a deserializzare try { var retData = JsonConvert.DeserializeObject(rawData); if (retData != null) { // verifico nId di risposta x salvare correttamente ProcessMan_m_AnswerReceived(retData); } } catch (Exception exc) { _logger.LogError($"Errore in fase decodifica messaggio da REDIS Channel{Environment.NewLine}{exc}"); } } } /// /// Wrapper per invio messaggio richieste calcolo via REDIS /// /// /// private void sendMessage(string notifyChannel, string message) { RedisChannel pubNotifyChannel = new RedisChannel(notifyChannel, RedisChannel.PatternMode.Literal); _currSub.Publish(pubNotifyChannel, message); } #endregion Private Methods } }