diff --git a/GWMS.UI/Controllers/IOBController.cs b/GWMS.UI/Controllers/IOBController.cs
new file mode 100644
index 0000000..41eeb0a
--- /dev/null
+++ b/GWMS.UI/Controllers/IOBController.cs
@@ -0,0 +1,1387 @@
+using GWMS.UI.Data;
+using Microsoft.AspNetCore.Mvc;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
+
+namespace GWMS.UI.Controllers
+{
+ ///
+ /// COntroller principale x dichiaraizone da IOB-WIN x GWMS
+ ///
+ /// per la conf dei verb http:
+ /// https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-5.0
+ /// https://stormpath.com/blog/routing-in-asp-net-core
+ ///
+ ///
+
+ [Route("api/[controller]")]
+ [ApiController]
+ public class IOBController : ControllerBase
+ {
+
+ public IOBController(GWMSDataService DataService)
+ {
+ _DataService = DataService;
+ }
+ protected GWMSDataService _DataService { get; set; }
+
+ // GET: api/IOB
+ [HttpGet]
+ public IEnumerable Get()
+ {
+ return new string[] { "value1", "value2" };
+ }
+
+ // GET api/IOB/5
+ [HttpGet("{id}")]
+ public string Get(int id)
+ {
+ return "value";
+ }
+
+ // POST api/IOB
+ [HttpPost]
+ public void Post([FromBody] string value)
+ {
+ }
+
+ // PUT api/IOB/5
+ [HttpPut("{id}")]
+ public void Put(int id, [FromBody] string value)
+ {
+ }
+
+ // DELETE api/IOB/5
+ [HttpDelete("{id}")]
+ public void Delete(int id)
+ {
+ }
+
+ ///
+ /// SALVA x macchina KVP parametro/valore:
+ ///
+ /// GET: IOB/addOptPar/SIMUL_03?pName=PZREQ&pValue=1000
+ ///
+ ///
+ ///
+ ///
+ ///
+ [HttpGet("addOptPar/{id}")]
+ public string addOptPar(string id, string pName, string pValue)
+ {
+ return $"N.A. | {id} | {pName} | {pValue}";
+ }
+
+ ///
+ /// AGGIUNGE TASK richiesto x macchina:
+ ///
+ /// GET: IOB/addTask2Exe/3010?taskName=startSetup&taskVal=T190406101512
+ /// GET: IOB/addTask2Exe/3010?taskName=stopSetup&taskVal=T190406101512
+ /// GET: IOB/addTask2Exe/SIMUL_03?taskName=setProg&taskVal=P00000001
+ /// GET: IOB/addTask2Exe/SIMUL_03?taskName=setComm&taskVal=ODL_0000123
+ /// GET: IOB/addTask2Exe/SIMUL_03?taskName=setArt&taskVal=ART_0000321
+ ///
+ ///
+ ///
+ ///
+ ///
+ [HttpGet("addTask2Exe/{id}")]
+ public string addTask2Exe(string id, string taskName, string taskVal)
+ {
+ return $"N.A. | {id} | {taskName} | {taskVal}";
+ }
+
+ ///
+ /// Verifica abilitazione PLANT
+ ///
+ /// GET: IOB/enabled/SIMUL_03
+ ///
+ ///
+ ///
+ [HttpGet("enabled/{id}")]
+ public async Task enabled(string id)
+ {
+ string answ = "ND";
+ // se id nullo --> KO!
+ if (id == null)
+ {
+ answ = "KO";
+ }
+ else
+ {
+
+
+ var ListRecords = await _DataService.PlantsGetAll();
+ var found = ListRecords.Where(x => x.PlantCode == id).Count();
+ answ = found > 0 ? "OK" : "NO";
+ }
+ return answ;
+ }
+
+#if false
+
+ #region Public Methods
+
+
+
+
+ ///
+ /// Processa una chiamata POST per l'invio di un array Json di oggetti input (EVENTI)
+ /// PUT: IOB/evListJson/SIMUL_03
+ ///
+ /// ID dell'IOB
+ ///
+ [HttpPost]
+ public string evListJson(string id)
+ {
+ int insDone = 0;
+ string answ = "-";
+ // questa classe è derivata da Controller.Response... x cui recupero lo stream in altro modo...
+ string content = "";
+ System.Web.HttpContext.Current.Request.InputStream.Position = 0;
+ using (var reader = new StreamReader(
+ Request.InputStream, System.Text.Encoding.UTF8, true, 4096, true))
+ {
+ content = reader.ReadToEnd();
+ }
+ //Rest
+ System.Web.HttpContext.Current.Request.InputStream.Position = 0;
+ // se ho dati...
+ if (content != "")
+ {
+ DataLayer DataLayerObj = new DataLayer();
+ // procedo a deserializzare in blocco l'oggetto...
+ evJsonPayload receivedData = new evJsonPayload();
+ try
+ {
+ // deserializzo.
+ receivedData = JsonConvert.DeserializeObject(content);
+ }
+ catch (Exception exc)
+ {
+ logger.lg.scriviLog($"Errore in fase deserializzazione inputJson{Environment.NewLine}{exc}");
+ answ = "NO";
+ }
+ // se ho qualcosa da processare...
+ if (receivedData != null)
+ {
+ // per ogni valore --> processo!
+ try
+ {
+ foreach (var item in receivedData.eventList)
+ {
+ if (memLayer.ML.CRI("_logLevel") > 6)
+ {
+ logger.lg.scriviLog($"Valori letti: idxMacchina: {id} | valore: {item.valore}", tipoLog.INFO);
+ }
+
+ // formato datetime come yyyyMMddHHmmssfff -->es: 20181223180600000
+ answ = DataLayerObj.processInput(id, item.valore, item.dtEve.ToString("yyyyMMddHHmmssfff"), item.dtCurr.ToString("yyyyMMddHHmmssfff"), item.cnt.ToString());
+ insDone++;
+ }
+ // se vuoto --> OK!
+ if (string.IsNullOrEmpty(answ))
+ {
+ answ = $"OK {insDone} processed";
+ }
+ }
+ catch (Exception exc)
+ {
+ logger.lg.scriviLog($"Errore in fase invio valori inputJson{Environment.NewLine}{exc}");
+ answ = "NO";
+ }
+ }
+ }
+ return answ;
+ }
+
+ // GET: IOB/flog/SIMUL_03?flux=PROG&valore=P0001&dtEve=20161223180600000&dtCurr=20161223180600000&cnt=999
+ public string flog(string id, string flux, string valore, string dtEve, string dtCurr, string cnt)
+ {
+ string answ = "";
+ // formato yyyymmddHHMMSSnnn ovvero da anno a millisecondi
+ if (cnt == null)
+ {
+ cnt = "0";
+ }
+
+ DateTime dataOraEvento = DateTime.Now;
+ if (memLayer.ML.CRI("_logLevel") > 6)
+ {
+ logger.lg.scriviLog($"Valori letti: idxMacchina: {id} | flux: {flux} valore: {valore}", tipoLog.INFO);
+ }
+ try
+ {
+ DataLayer DataLayerObj = new DataLayer();
+ int count = 0;
+ Int32.TryParse(cnt, out count);
+ answ = DataLayerObj.processFluxLog(id, flux, valore, dtEve, dtCurr, count);
+ }
+ catch (Exception exc)
+ {
+ logger.lg.scriviLog($"Errore in flog{Environment.NewLine}{exc}");
+ answ = "NO";
+ }
+ return answ;
+ }
+
+ ///
+ /// Processa una chiamata POST per l'invio di un array Json di oggetti fluxLog
+ /// PUT: IOB/flogJson/SIMUL_03
+ ///
+ /// ID dell'IOB
+ ///
+ [HttpPost]
+ public string flogJson(string id)
+ {
+ int insDone = 0;
+ string answ = "-";
+ // questa classe è derivata da Controller.Response... x cui recupero lo stream in altro modo...
+ string content = "";
+ System.Web.HttpContext.Current.Request.InputStream.Position = 0;
+ using (var reader = new StreamReader(
+ Request.InputStream, System.Text.Encoding.UTF8, true, 4096, true))
+ {
+ content = reader.ReadToEnd();
+ }
+ //Rest
+ System.Web.HttpContext.Current.Request.InputStream.Position = 0;
+ // se ho dati...
+ if (content != "")
+ {
+ DataLayer DataLayerObj = new DataLayer();
+ // procedo a deserializzare in blocco l'oggetto...
+ flogJsonPayload receivedData = new flogJsonPayload();
+ try
+ {
+ // deserializzo.
+ receivedData = JsonConvert.DeserializeObject(content);
+ }
+ catch (Exception exc)
+ {
+ logger.lg.scriviLog($"Errore in fase deserializzazione flogJson{Environment.NewLine}{exc}");
+ answ = "NO";
+ }
+ // se ho qualcosa da processare...
+ if (receivedData != null)
+ {
+ // per ogni valore --> salvo!
+ try
+ {
+ foreach (var item in receivedData.fluxData)
+ {
+ // formato datetime come yyyyMMddHHmmssfff -->es: 20181223180600000
+ answ = DataLayerObj.processFluxLog(id, item.flux, item.valore, item.dtEve.ToString("yyyyMMddHHmmssfff"), item.dtCurr.ToString("yyyyMMddHHmmssfff"), item.cnt);
+ }
+ // se vuoto --> OK!
+ if (string.IsNullOrEmpty(answ))
+ {
+ answ = $"OK {insDone} processed";
+ }
+ }
+ catch (Exception exc)
+ {
+ logger.lg.scriviLog($"Errore in fase invio valori flogJson{Environment.NewLine}{exc}");
+ answ = "NO";
+ }
+ // leggo parametri correnti...
+ try
+ {
+ List currParams = DataLayerObj.getCurrObjItems(id);
+ // ora per ogni valore RICEVUTO costruisco un oggetto in innnovazioni da inviare...x salvare in stato parametri...
+ List innovazioni = new List();
+ foreach (var item in receivedData.fluxData)
+ {
+ // flux = uuid del parametro
+ objItem trovato = currParams.Find(obj => obj.uid == item.flux);
+ // se lo trovo aggiorno...
+ if (trovato != null)
+ {
+ // aggiorno valore e data
+ trovato.value = item.valore;
+ trovato.lastRead = DateTime.Now;
+ // se fosse un valore WRITE e mi ha dato un valore vuoto --> mando un fix x riscrittura
+ if (trovato.writable && string.IsNullOrEmpty(item.valore))
+ {
+ taskType currTask = (taskType)Enum.Parse(typeof(taskType), trovato.uid);
+ DataLayerObj.addCheckTask4Machine(id, currTask, item.valore);
+ }
+ }
+ // altrimenti AGGIUNGO (READ ONLY)...
+ else
+ {
+ trovato = new objItem
+ {
+ uid = item.flux,
+ name = item.flux,
+ value = item.valore,
+ lastRead = DateTime.Now,
+ writable = false
+ };
+ }
+ // lo carico in innovation
+ innovazioni.Add(trovato);
+ }
+ // faccio upsert innovations!
+ DataLayerObj.upsertCurrObjItems(id, innovazioni);
+ }
+ catch (Exception exc)
+ {
+ logger.lg.scriviLog($"Errore in fase salvataggio innovazioni parametri correnti da flogJson{Environment.NewLine}{exc}");
+ answ = "NO";
+ }
+ }
+ }
+ return answ;
+ }
+
+ ///
+ /// Chiude ODL precedente ed avvia uno nuovo (duplicandolo e sitemando quantità RIMANENTE), e CONFERMA produzione...
+ ///
+ /// GET: IOB/forceSplitOdl/SIMUL_03
+ ///
+ ///
+ ///
+ /// Esito chiamata (OK/vuoto)
+ public string forceSplitOdl(string id)
+ {
+ // attenzione! poiché nell'URL il carattere "#" fiene filtrato ci aspettiamo il carattere "|" che poi trasformiamo ora in "#"
+ id = id.Replace("|", "#");
+ DataLayer DataLayerObj = new DataLayer();
+ return DataLayerObj.AutoStartOdl(id, true, true, 100, "");
+ }
+
+ ///
+ /// Chiude ODL precedente ed avvia uno nuovo (duplicandolo e sitemando quantità RIMANENTE), e CONFERMA produzione...
+ ///
+ /// GET: IOB/forceSplitOdl/SIMUL_03?doConfirm=true&qtyFromLast=true&roundStep=150&extOrderCode=ABCDE1234
+ ///
+ ///
+ /// id impianto
+ ///
+ ///
+ ///
+ /// Cod esterno da legare all'ODL x tracciare lotti prod
+ /// Esito chiamata (OK/vuoto)
+ public string forceSplitOdlFull(string id, bool doConfirm, bool qtyFromLast, int? roundStep, string keyRichiesta = "")
+ {
+ // attenzione! poiché nell'URL il carattere "#" fiene filtrato ci aspettiamo il carattere "|" che poi trasformiamo ora in "#"
+ id = id.Replace("|", "#");
+ DataLayer DataLayerObj = new DataLayer();
+ if (roundStep == null)
+ {
+ roundStep = 100;
+ }
+ return DataLayerObj.AutoStartOdl(id, doConfirm, qtyFromLast, (int)roundStep, keyRichiesta);
+ }
+
+ ///
+ /// Recupera COUNTER x macchina:
+ ///
+ /// GET: IOB/getCounter/5
+ ///
+ ///
+ ///
+ ///
+ public string getCounter(string id)
+ {
+ string answ = "";
+ try
+ {
+ DataLayer DataLayerObj = new DataLayer();
+ answ = DataLayerObj.pzCounter(id).ToString();
+ }
+ catch (Exception exc)
+ {
+ logger.lg.scriviLog(string.Format("Errore in counter (get){0}{1}", Environment.NewLine, exc));
+ answ = "NO";
+ }
+ return answ;
+ }
+
+ ///
+ /// Recupera COUNTER x macchina dal CONTEGGIO dei TCRecorded:
+ ///
+ /// GET: IOB/getCounterTCRec/5
+ ///
+ ///
+ ///
+ ///
+ public string getCounterTCRec(string id)
+ {
+ string answ = "";
+ try
+ {
+ DataLayer DataLayerObj = new DataLayer();
+ answ = DataLayerObj.pzCounterTC(id).ToString();
+ }
+ catch (Exception exc)
+ {
+ logger.lg.scriviLog(string.Format("Errore in counter TC (get){0}{1}", Environment.NewLine, exc));
+ answ = "NO";
+ }
+ return answ;
+ }
+
+ ///
+ /// Recupera DATI correnti x macchina:
+ ///
+ /// GET: IOB/getCurrData/SIMUL_03
+ ///
+ ///
+ ///
+ /// Json contenente la riga di stato macchina
+ public string getCurrData(string id)
+ {
+ // attenzione! poiché nell'URL il carattere "#" fiene filtrato ci aspettiamo il carattere "|" che poi trasformiamo ora in "#"
+ id = id.Replace("|", "#");
+ string answ = "";
+ // scrivo keep alive!!! (se necessario, altrimenti è in cache...)
+ MapoDb.MapoDb connDb = new MapoDb.MapoDb();
+ DataLayer DataLayerObj = new DataLayer();
+ connDb.scriviKeepAlive(id, DateTime.Now);
+ try
+ {
+ // recupero dati macchina...
+ Dictionary valori = DataLayerObj.mDatiMacchine(id);
+ answ = JsonConvert.SerializeObject(valori);
+ }
+ catch
+ { }
+ return answ;
+ }
+
+ ///
+ /// Recupera ODL corrente x macchina:
+ ///
+ /// GET: IOB/getCurrODL/SIMUL_03
+ ///
+ ///
+ ///
+ ///
+ public string getCurrODL(string id)
+ {
+ // attenzione! poiché nell'URL il carattere "#" viene filtrato ci aspettiamo il carattere "|" che poi trasformiamo ora in "#"
+ id = id.Replace("|", "#");
+ string answ = "";
+ try
+ {
+ DataLayer DataLayerObj = new DataLayer();
+ answ = $"{DataLayerObj.currODL(id)}";
+ }
+ catch (Exception exc)
+ {
+ logger.lg.scriviLog(string.Format("Errore in currODL (get){0}{1}", Environment.NewLine, exc));
+ answ = "NO";
+ }
+ return answ;
+ }
+
+ ///
+ /// Restituisce intera riga dell'odl correntemente in lavorazione sulla macchina...
+ /// GET: IOB/getCurrOdlRow/SIMUL_01
+ ///
+ ///
+ ///
+ public string getCurrOdlRow(string id)
+ {
+ // attenzione! poiché nell'URL il carattere "#" fiene filtrato ci aspettiamo il carattere "|" che poi trasformiamo ora in "#"
+ id = id.Replace("|", "#");
+ string answ = "";
+ DS_ProdTempi.ODLDataTable currData = null;
+ // chiamo metodo redis/db...
+ try
+ {
+ DataLayer DataLayerObj = new DataLayer();
+ currData = DataLayerObj.currODLRowTab(id);
+ answ = JsonConvert.SerializeObject(currData);
+ }
+ catch (Exception exc)
+ {
+ logger.lg.scriviLog($"Eccezione in recupero getCurrOdlRow{Environment.NewLine}{exc}", tipoLog.EXCEPTION);
+ }
+ return answ;
+ }
+
+ ///
+ /// Restituisce data-ora inizio dell'odl correntemente in lavorazione sulla macchina...
+ /// es: http://url_site/MP/IO/IOB/getCurrOdlStart/SIMUL_03
+ ///
+ ///
+ ///
+ public string getCurrOdlStart(string id)
+ {
+ // attenzione! poiché nell'URL il carattere "#" fiene filtrato ci aspettiamo il carattere "|" che poi trasformiamo ora in "#"
+ id = id.Replace("|", "#");
+ DateTime answ = new DateTime(DateTime.Now.Year - 1, 12, 31);
+ // chiamo metodo redis/db...
+ try
+ {
+ DataLayer DataLayerObj = new DataLayer();
+ DS_ProdTempi.ODLDataTable currTab = DataLayerObj.currODLRowTab(id);
+ if (currTab.Count > 0)
+ {
+ DS_ProdTempi.ODLRow odlRow = currTab[0];
+ answ = odlRow.DataInizio;
+ }
+ }
+ catch (Exception exc)
+ {
+ logger.lg.scriviLog($"Eccezione in recupero getCurrOdlStart{Environment.NewLine}{exc}", tipoLog.EXCEPTION);
+ }
+ return answ.ToString("yyyy-MM-dd HH:mm:ss");
+ }
+
+ ///
+ /// Restituisce intera riga dello stato di macchina...
+ /// GET: IOB/getCurrStatoRow/SIMUL_01
+ ///
+ ///
+ ///
+ public string getCurrStatoRow(string id)
+ {
+ // attenzione! poiché nell'URL il carattere "#" fiene filtrato ci aspettiamo il carattere "|" che poi trasformiamo ora in "#"
+ id = id.Replace("|", "#");
+ string answ = "";
+ DS_applicazione.StatoMacchineDataTable currData = null;
+ // chiamo metodo redis/db...
+ try
+ {
+ DataLayer DataLayerObj = new DataLayer();
+ currData = DataLayerObj.currSMTab(id);
+ answ = JsonConvert.SerializeObject(currData);
+ }
+ catch (Exception exc)
+ {
+ logger.lg.scriviLog($"Eccezione in recupero getCurrStatoRow{Environment.NewLine}{exc}", tipoLog.EXCEPTION);
+ }
+ return answ;
+ }
+
+ ///
+ /// Restituisce un array JSon di files di una IOB
+ /// PUT: IOB/getFiles/SIMUL_03
+ ///
+ /// ID dell'IOB
+ /// Oggetto Json in formato MapoSDK.fileEmbed
+ public string getFiles(string id)
+ {
+ string answ = "";
+ // procedo a deserializzare in blocco l'oggetto...
+ try
+ {
+ // recupero TUTTI i files della folder dell'IOB richiesta
+ string basePath = Server.MapPath(memLayer.ML.CRS("uploadFileDir"));
+ string dirPath = $"{basePath}\\{id}";
+ var fileList = fileMover.obj.elencoFilesDir(dirPath);
+ fileEmbed objFiles = new fileEmbed();
+ MapoSDK.smallFile currFile = null;
+ string fileContent = "";
+ foreach (var item in fileList)
+ {
+ fileContent = System.IO.File.ReadAllText($"{dirPath}\\{item.Nome}");
+ currFile = new MapoSDK.smallFile()
+ {
+ fileName = item.Nome,
+ content = fileContent.Replace("\r\n", Environment.NewLine)
+ };
+ objFiles.fileList.Add(currFile);
+ }
+ // serializzo
+ answ = JsonConvert.SerializeObject(objFiles);
+ }
+ catch (Exception exc)
+ {
+ logger.lg.scriviLog($"Errore in uploadFile{Environment.NewLine}{exc}");
+ answ = "NO";
+ }
+ return answ;
+ }
+
+ ///
+ /// Restituisce il valore dello stato di IDLE della macchina, quindi SOLO SE NON é in lavoro e già convertito in minuti...
+ /// GET: IOB/getIdlePeriod/SIMUL_01
+ ///
+ ///
+ ///
+ public int getIdlePeriod(string id)
+ {
+ // attenzione! poiché nell'URL il carattere "#" fiene filtrato ci aspettiamo il carattere "|" che poi trasformiamo ora in "#"
+ id = id.Replace("|", "#");
+ int answ = 0;
+ DataLayer DataLayerObj = new DataLayer();
+ DS_applicazione.StatoMacchineDataTable currData = null;
+ // chiamo metodo redis/db...
+ try
+ {
+ currData = DataLayerObj.currSMTab(id);
+ if (currData.Count > 0)
+ {
+ // recupero da redis elenco stati
+ DS_applicazione.AnagraficaStatiDataTable anagStati = DataLayerObj.AnagraficaStati();
+ DS_applicazione.AnagraficaStatiRow currStato = anagStati.FindByIdxStato(currData[0].IdxStato);
+ // calcolo SE sia idle... OVVERO SEMAFORO NON VERDE!!!
+ if (currStato.Semaforo != "sVe")
+ {
+ // calcolo durata...
+ answ = (int)DateTime.Now.Subtract(currData[0].InizioStato).TotalMinutes;
+ }
+ }
+ }
+ catch (Exception exc)
+ {
+ logger.lg.scriviLog($"Eccezione in recupero getIdlePeriod{Environment.NewLine}{exc}", tipoLog.EXCEPTION);
+ }
+ return answ;
+ }
+
+ ///
+ /// Restituisce il (primo) codice IOB da dover gestire (se un IOBMAN chiede di gestirne uno in +...)
+ ///
+ /// IP del Gateway
+ ///
+ public string getIob2call(string GWIP)
+ {
+ string answ = "";
+
+ // !!!FARE!!! temporanemanete genera a caso vuoto o 3000 x permettere test... altrimenti gestisce VERA coda... secondi pari...
+ int resto = 0;
+ Math.DivRem(DateTime.Now.Second, 2, out resto);
+ if (resto == 0)
+ {
+ answ = "3000";
+ }
+
+ return answ;
+ }
+
+ ///
+ /// Restituisce dati di associazione tra macchina, device IOB chiamante e sue info
+ ///
+ /// Id della macchina
+ ///
+ public string getM2IOB(string id)
+ {
+ string answ = "";
+ try
+ {
+ // recupero da redis...
+ string hM2IOB = DataLayer.hM2IOB(id);
+ string dataSer = memLayer.ML.getRSV(hM2IOB);
+ if (dataSer != "" && dataSer != null)
+ {
+ // restituisco Json
+ answ = dataSer;
+ }
+ else
+ {
+ answ = "NO";
+ }
+ }
+ catch
+ {
+ answ = "KO";
+ }
+ return answ;
+ }
+
+ ///
+ /// restituisce elenco parametri correnti come una List Json di oggetti objItem
+ /// GET: IOB/getObjItems/SIMUL_03
+ ///
+ /// ID dell'IOB
+ ///
+ public string getObjItems(string id)
+ {
+ string answ = "";
+ if (string.IsNullOrWhiteSpace(id))
+ {
+ answ = "Missing IOB";
+ }
+ else
+ {
+ // procedo a recuperare l'oggetto...
+ List currParams = new List();
+ try
+ {
+ DataLayer DataLayerObj = new DataLayer();
+ // deserializzo.
+ currParams = DataLayerObj.getCurrObjItems(id);
+ // se != null --> salvo!
+ if (currParams != null)
+ {
+ answ = JsonConvert.SerializeObject(currParams);
+ }
+ }
+ catch (Exception exc)
+ {
+ logger.lg.scriviLog($"Errore in getCurrParams{Environment.NewLine}{exc}");
+ answ = "NO";
+ }
+ }
+ return answ;
+ }
+
+ ///
+ /// restituisce elenco parametri CHE RICHIEDONO scrittura su PLC come una List Json di oggetti objItem
+ /// GET: IOB/getObjItems2Write/SIMUL_03
+ ///
+ /// ID dell'IOB
+ ///
+ public string getObjItems2Write(string id)
+ {
+ string answ = "";
+ if (string.IsNullOrWhiteSpace(id))
+ {
+ answ = "Missing IOB";
+ }
+ else
+ {
+ // procedo a recuperare l'oggetto...
+ List currParams = new List();
+ try
+ {
+ DataLayer DataLayerObj = new DataLayer();
+ // deserializzo.
+ currParams = DataLayerObj.getCurrObjItemsPendigWrite(id);
+ // se != null --> salvo!
+ if (currParams != null)
+ {
+ answ = JsonConvert.SerializeObject(currParams);
+ }
+ }
+ catch (Exception exc)
+ {
+ logger.lg.scriviLog($"Errore in getCurrParams{Environment.NewLine}{exc}");
+ answ = "NO";
+ }
+ }
+ return answ;
+ }
+
+ ///
+ /// Recupera TASK richiesto x macchina:
+ ///
+ /// GET: IOB/getOptPar/SIMUL_03
+ ///
+ ///
+ ///
+ /// Json contenente 1..n task da eseguire
+ public string getOptPar(string id)
+ {
+ string answ = "";
+ // scrivo keep alive!!! (se necessario, altrimenti è in cache...)
+ MapoDb.MapoDb connDb = new MapoDb.MapoDb();
+ connDb.scriviKeepAlive(id, DateTime.Now);
+ try
+ {
+ // leggo da REDIS eventuale elenco task x macchina...
+ DataLayer DataLayerObj = new DataLayer();
+ Dictionary valori = DataLayerObj.mOptParMacchina(id);
+ answ = JsonConvert.SerializeObject(valori);
+ }
+ catch
+ { }
+ return answ;
+ }
+
+ ///
+ /// Recupera TASK richiesto x macchina:
+ ///
+ /// GET: IOB/getTask2Exe/SIMUL_03
+ ///
+ ///
+ ///
+ /// Json contenente 1..n task da eseguire
+ public string getTask2Exe(string id)
+ {
+ string answ = "";
+ // scrivo keep alive!!! (se necessario, altrimenti è in cache...)
+ MapoDb.MapoDb connDb = new MapoDb.MapoDb();
+ DataLayer DataLayerObj = new DataLayer();
+ connDb.scriviKeepAlive(id, DateTime.Now);
+ try
+ {
+ // leggo da REDIS eventuale elenco task x macchina...
+ Dictionary valori = DataLayerObj.mTaskMacchina(id);
+ answ = JsonConvert.SerializeObject(valori);
+ }
+ catch
+ { }
+ return answ;
+ }
+
+ // GET: IOB (è un check alive del server)
+ public string Index()
+ {
+ if (memLayer.ML.CRB("IOB_RedEnab"))
+ {
+ // conto la richiesta nel contatore REDIS
+ long nCall = memLayer.ML.setRCntI(DataLayer.mHash("COUNT:pCall:IOB_INDEX"));
+ //... se == nCall2Log scrivo su log e resetto
+ long nCall2Log = memLayer.ML.cdvi("nCall2Log");
+ if (nCall >= nCall2Log)
+ {
+ // loggo
+ logger.lg.scriviLog(string.Format("IOB_INDEX: effettuate {0} call", nCall), tipoLog.INFO);
+ // resetto!
+ memLayer.ML.resetRCnt(DataLayer.mHash("COUNT:pCall:IOB_INDEX"));
+ }
+ }
+ return "OK";
+ }
+
+ // GET: IOB/input/SIMUL_03?valore=3&dtEve=20181206180600000&dtCurr=20181206180600000&cnt=999
+ public string input(string id, string valore, string dtEve, string dtCurr, string cnt)
+ {
+ string answ = "";
+ // formato yyyymmddHHMMSSnnn ovvero da anno a millisecondi
+ if (cnt == null)
+ {
+ cnt = "0";
+ }
+
+ DateTime dataOraEvento = DateTime.Now;
+ if (memLayer.ML.CRI("_logLevel") > 6)
+ {
+ logger.lg.scriviLog($"Valori letti: idxMacchina: {id} | valore: {valore}", tipoLog.INFO);
+ }
+ try
+ {
+ DataLayer DataLayerObj = new DataLayer();
+ answ = DataLayerObj.processInput(id, valore, dtEve, dtCurr, cnt);
+ }
+ catch (Exception exc)
+ {
+ logger.lg.scriviLog(string.Format("Errore in processInput{0}{1}", Environment.NewLine, exc));
+ answ = "NO";
+ }
+ return answ;
+ }
+
+ ///
+ /// Processa una chiamata POST per l'invio di un array Json di oggetti LIVE REC
+ /// PUT: IOB/liveJson/SIMUL_03
+ ///
+ /// ID dell'IOB
+ ///
+ [HttpPost]
+ public string liveJson(string id)
+ {
+ string answ = "-";
+ // questa classe è derivata da Controller.Response... x cui recupero lo stream in altro modo...
+ string content = "";
+ System.Web.HttpContext.Current.Request.InputStream.Position = 0;
+ using (var reader = new StreamReader(
+ Request.InputStream, System.Text.Encoding.UTF8, true, 4096, true))
+ {
+ content = reader.ReadToEnd();
+ }
+ //Rest
+ System.Web.HttpContext.Current.Request.InputStream.Position = 0;
+ // procedo a deserializzare in blocco l'oggetto...
+ try
+ {
+ // deserializzo.
+ liveIOB receivedData = JsonConvert.DeserializeObject(content);
+ DataLayer DataLayerObj = new DataLayer();
+ answ = DataLayerObj.processLiveJson(id, receivedData);
+ // se vuoto --> OK!
+ if (string.IsNullOrEmpty(answ))
+ {
+ answ = "OK 1 done";
+ }
+ }
+ catch (Exception exc)
+ {
+ logger.lg.scriviLog($"Errore in liveJson{Environment.NewLine}{exc}");
+ answ = "NO";
+ }
+ return answ;
+ }
+
+ // GET: IOB/liveRec/SIMUL_03?&liveData=chiave1|valore1#chiave2|valore#|chiave3|valore3
+ public string liveRec(string id, string liveData)
+ {
+ string answ = "";
+ DateTime dataOraEvento = DateTime.Now;
+ if (memLayer.ML.CRI("_logLevel") > 6)
+ {
+ logger.lg.scriviLog($"Valori Live:{Environment.NewLine}idxMacchina: {id}{Environment.NewLine}liveData: {liveData}", tipoLog.INFO);
+ }
+ try
+ {
+ DataLayer DataLayerObj = new DataLayer();
+ answ = DataLayerObj.processLiveRec(id, liveData);
+ }
+ catch (Exception exc)
+ {
+ logger.lg.scriviLog($"Errore in liveRec{Environment.NewLine}{exc}");
+ answ = "NO";
+ }
+ return answ;
+ }
+
+ ///
+ /// ELIMINA TASK richiesto x macchina:
+ ///
+ /// GET: IOB/remOptPar/SIMUL_03?pName=PZREQ
+ ///
+ ///
+ ///
+ ///
+ public string remOptPar(string id, string pName)
+ {
+ string answ = "";
+ // scrivo keep alive!!! (se necessario, altrimenti è in cache...)
+ MapoDb.MapoDb connDb = new MapoDb.MapoDb();
+ DataLayer DataLayerObj = new DataLayer();
+ connDb.scriviKeepAlive(id, DateTime.Now);
+ try
+ {
+ DataLayerObj.remOptPar4Machine(id, pName);
+ answ = getOptPar(id);
+ }
+ catch
+ { }
+ return answ;
+ }
+
+ ///
+ /// ELIMINA TASK richiesto x macchina:
+ ///
+ /// GET: IOB/remTask2Exe/SIMUL_03?taskName=T180326160502
+ ///
+ ///
+ ///
+ ///
+ public string remTask2Exe(string id, string taskName)
+ {
+ string answ = "";
+ // scrivo keep alive!!! (se necessario, altrimenti è in cache...)
+ MapoDb.MapoDb connDb = new MapoDb.MapoDb();
+ DataLayer DataLayerObj = new DataLayer();
+ connDb.scriviKeepAlive(id, DateTime.Now);
+ try
+ {
+ // converto stringa in tipo task...
+ taskType tName = taskType.nihil;
+ bool fatto = Enum.TryParse(taskName, out tName);
+ if (fatto)
+ {
+ DataLayerObj.remTask4Machine(id, tName);
+ }
+ else
+ {
+ logger.lg.scriviLog($"remTask2Exe: impossibile riconoscere il comando {taskName} come uno deitipi ammessi, NON aggiunto", tipoLog.ERROR);
+ }
+ answ = getTask2Exe(id);
+ }
+ catch
+ { }
+ return answ;
+ }
+
+ ///
+ /// Effettua RESET dell'ODL corrente x macchina:
+ ///
+ /// GET: IOB/resetCurrODL/5
+ ///
+ ///
+ ///
+ ///
+ public string resetCurrODL(string id)
+ {
+ DataLayer DataLayerObj = new DataLayer();
+ return DataLayerObj.emptyCurrODL(id);
+ }
+
+ ///
+ /// Processa una chiamata POST per l'invio di un array Json di oggetti plcMemConf
+ /// PUT: IOB/saveConf/SIMUL_03
+ ///
+ /// ID dell'IOB
+ ///
+ [HttpPost]
+ public string saveConf(string id)
+ {
+ string answ = "";
+ if (string.IsNullOrWhiteSpace(id))
+ {
+ answ = "Missing IOB";
+ }
+ else
+ {
+ // questa classe è derivata da Controller.Response... x cui recupero lo stream in altro modo...
+ string content = "";
+ System.Web.HttpContext.Current.Request.InputStream.Position = 0;
+ using (var reader = new StreamReader(Request.InputStream, System.Text.Encoding.UTF8, true, 4096, true))
+ {
+ content = reader.ReadToEnd();
+ }
+ //Rest
+ System.Web.HttpContext.Current.Request.InputStream.Position = 0;
+ // procedo a deserializzare in blocco l'oggetto...
+ plcMemMap currMemMap = null;
+ try
+ {
+ // deserializzo.
+ currMemMap = JsonConvert.DeserializeObject(content);
+ // se != null --> salvo!
+ if (currMemMap != null)
+ {
+ DataLayer DataLayerObj = new DataLayer();
+ DataLayerObj.setIobMemMap(id, currMemMap);
+ answ = "OK";
+ }
+ }
+ catch
+ { }
+ }
+ return answ;
+ }
+
+ ///
+ /// Processa una chiamata POST per l'invio di un array Json di oggetti di conf DataItems (es per MTC)
+ /// PUT: IOB/saveDataItems/SIMUL_03
+ ///
+ /// ID dell'IOB
+ ///
+ [HttpPost]
+ public string saveDataItems(string id)
+ {
+ string answ = "";
+ if (string.IsNullOrWhiteSpace(id))
+ {
+ answ = "Missing IOB";
+ }
+ else
+ {
+ // questa classe è derivata da Controller.Response... x cui recupero lo stream in altro modo...
+ string content = "";
+ System.Web.HttpContext.Current.Request.InputStream.Position = 0;
+ using (var reader = new StreamReader(Request.InputStream, System.Text.Encoding.UTF8, true, 4096, true))
+ {
+ content = reader.ReadToEnd();
+ }
+ // Rest
+ System.Web.HttpContext.Current.Request.InputStream.Position = 0;
+
+ // procedo a deserializzare in blocco l'oggetto...
+ List dataItems = null;
+ try
+ {
+ // deserializzo.
+ dataItems = JsonConvert.DeserializeObject>(content);
+ // se != null --> salvo!
+ if (dataItems != null)
+ {
+ // chiamo metodo update direttamente!
+ MtcDataModelArchive.man.saveMachineDataItems(id, dataItems);
+ answ = "OK";
+ }
+ }
+ catch
+ { }
+ }
+ return answ;
+ }
+
+ ///
+ /// SALVA in blocco un incremento pezzi x macchina restituendo il valore appena inviato o, se mancasse chaive redis, del valore da DB
+ ///
+ /// GET: IOB/savePzCountInc/5?qty=10
+ ///
+ ///
+ /// codice macchina
+ /// num peziz da salvare in blocco
+ ///
+ public string savePzCountInc(string id, string qty)
+ {
+ string answ = "";
+ DateTime dataOraEvento = DateTime.Now;
+ // salvo SEMPRE log x questo tipo di dati!
+ logger.lg.scriviLog($"Salvataggio incremento contapezzi:{Environment.NewLine}idxMacchina: {id}{Environment.NewLine}pezzi: {qty}", tipoLog.INFO);
+ try
+ {
+ DataLayer DataLayerObj = new DataLayer();
+ answ = DataLayerObj.saveCaricoPezzi(id, qty);
+ }
+ catch (Exception exc)
+ {
+ logger.lg.scriviLog($"Errore in savePzCountInc{Environment.NewLine}{exc}");
+ answ = "NO";
+ }
+ return answ;
+ }
+
+ ///
+ /// Salva IP del gateway dopo il reboot
+ ///
+ /// IP del Gateway
+ ///
+ public string sendRebootGateway(string GWIP)
+ {
+ string answ = "OK";
+
+ // !!!FARE!!! deve salvare il riavvio dell'applicazione GATEWAY multiclient
+
+ return answ;
+ }
+
+ ///
+ /// SALVA Counter x macchina restituendo il valore appena inviato o, se mancasse chiave redis, del valore da DB
+ ///
+ /// GET: IOB/setCounter/5?counter=10
+ ///
+ ///
+ /// cod macchina
+ /// contapezzi da salvare
+ ///
+ public string setCounter(string id, string counter)
+ {
+ string answ = "-1";
+ DateTime dataOraEvento = DateTime.Now;
+ if (memLayer.ML.CRI("_logLevel") > 6)
+ {
+ logger.lg.scriviLog($"Salvataggio counter | idxMacchina: {id}", tipoLog.INFO);
+ }
+ try
+ {
+ DataLayer DataLayerObj = new DataLayer();
+ answ = DataLayerObj.saveCounter(id, counter);
+ }
+ catch (Exception exc)
+ {
+ logger.lg.scriviLog(string.Format("Errore in counter (set){0}{1}", Environment.NewLine, exc));
+ }
+ return answ;
+ }
+
+ ///
+ /// Salva associazione tra macchina, device IOB chiamante e sue info
+ ///
+ /// Id della macchina
+ /// Nome dell'IOB di acquisizione della macchina
+ ///
+ public string setM2IOB(string id, string IOB_name)
+ {
+ string answ = "";
+ try
+ {
+ // recupero IP del client remoto
+ string IPv4 = Request.UserHostName;
+ string agent = Request.UserAgent;
+ // creo oggetto IOB_data...
+ IOB_data m2IOB = new IOB_data
+ {
+ name = IOB_name,
+ IP = IPv4,
+ iType = IobType.ND,
+ typeCss = "fa fa-question-circle-o",
+ CNC_Counter = false
+ };
+ // imposto tipo ed icona come windows/linux secondo UserAgent...
+ if (agent.IndexOf("WIN") >= 0)
+ {
+ m2IOB.iType = IobType.WIN;
+ m2IOB.typeCss = "fa fa-windows";
+ m2IOB.CNC_Counter = true;
+ }
+ else if (agent.IndexOf("Python") >= 0)
+ {
+ m2IOB.iType = IobType.rPi;
+ m2IOB.typeCss = "fa fa-linux";
+ }
+ // serializzo...
+ string dataSer = JsonConvert.SerializeObject(m2IOB);
+ // salvo in redis...
+ string hM2IOB = DataLayer.hM2IOB(id);
+ memLayer.ML.setRSV(hM2IOB, dataSer);
+ // salvo tutto OK
+ answ = "OK";
+ }
+ catch
+ {
+ answ = "KO";
+ }
+ return answ;
+ }
+
+ ///
+ /// Processa una chiamata POST per l'invio di una List Json di oggetti objItem
+ /// POST: IOB/setObjItems/SIMUL_03
+ ///
+ /// ID dell'IOB
+ ///
+ [HttpPost]
+ public string setObjItems(string id)
+ {
+ string answ = "";
+ if (string.IsNullOrWhiteSpace(id))
+ {
+ answ = "Missing IOB";
+ }
+ else
+ {
+ // questa classe è derivata da Controller.Response... x cui recupero lo stream in altro modo...
+ string content = "";
+ System.Web.HttpContext.Current.Request.InputStream.Position = 0;
+ using (var reader = new StreamReader(Request.InputStream, System.Text.Encoding.UTF8, true, 4096, true))
+ {
+ content = reader.ReadToEnd();
+ }
+ //Rest
+ System.Web.HttpContext.Current.Request.InputStream.Position = 0;
+ // procedo a deserializzare in blocco l'oggetto...
+ List currParams = new List();
+ try
+ {
+ // deserializzo.
+ currParams = JsonConvert.DeserializeObject>(content);
+ // se != null --> salvo!
+ if (currParams != null)
+ {
+ DataLayer DataLayerObj = new DataLayer();
+ DataLayerObj.setCurrObjItems(id, currParams);
+ answ = "OK";
+ }
+ }
+ catch (Exception exc)
+ {
+ logger.lg.scriviLog($"Errore in setCurrParams{Environment.NewLine}{exc}");
+ answ = "NO";
+ }
+ }
+ return answ;
+ }
+
+ // GET: IOB/slog/SIMUL_03
+ public string slog(string id)
+ {
+ string answ = "ND";
+ // se id nullo --> KO!
+ if (id == null)
+ {
+ answ = "KO";
+ }
+ else
+ {
+ try
+ {
+ DataLayer DataLayerObj = new DataLayer();
+ // salvo risposta
+ answ = DataLayerObj.sLogEnab(id) ? "OK" : "NO";
+ }
+ catch (Exception exc)
+ {
+ logger.lg.scriviLog(string.Format("Errore in sLog{0}{1}", Environment.NewLine, exc));
+ answ = "NO";
+ }
+ }
+ return answ;
+ }
+
+ ///
+ /// Processa una chiamata POST per l'invio di un SET di file "a nome" di un IOB, formato MapoSDK.fileEmbed
+ /// PUT: IOB/uploadFile/SIMUL_03
+ ///
+ /// ID dell'IOB
+ ///
+ [HttpPost]
+ public string uploadFile(string id)
+ {
+ string answ = "";
+ // questa classe è derivata da Controller.Response... x cui recupero lo stream in altro modo...
+ string content = "";
+ System.Web.HttpContext.Current.Request.InputStream.Position = 0;
+ using (var reader = new StreamReader(Request.InputStream, System.Text.Encoding.UTF8, true, 4096, true))
+ {
+ content = reader.ReadToEnd();
+ }
+ //Rest
+ System.Web.HttpContext.Current.Request.InputStream.Position = 0;
+ // procedo a deserializzare in blocco l'oggetto...
+ try
+ {
+ // deserializzo.
+ fileEmbed receivedData = JsonConvert.DeserializeObject(content);
+ // salvo nella cartella di Upload...
+ string basePath = Server.MapPath(memLayer.ML.CRS("uploadFileDir"));
+ string dirPath = $"{basePath}\\{id}";
+ // fix directory...
+ Directory.CreateDirectory(dirPath);
+ foreach (var item in receivedData.fileList)
+ {
+ // scrivo!
+ System.IO.File.WriteAllText($"{dirPath}\\{item.fileName}", item.content);
+ }
+ answ = "OK";
+ }
+ catch (Exception exc)
+ {
+ logger.lg.scriviLog($"Errore in uploadFile{Environment.NewLine}{exc}");
+ answ = "NO";
+ }
+ return answ;
+ }
+
+ ///
+ /// Processa una chiamata POST per l'invio di una List Json di UNO O PIU' oggetti objItem
+ /// POST: IOB/upsertObjItems/SIMUL_03
+ ///
+ /// ID dell'IOB
+ ///
+ [HttpPost]
+ public string upsertObjItems(string id)
+ {
+ string answ = "";
+ if (string.IsNullOrWhiteSpace(id))
+ {
+ answ = "Missing IOB";
+ }
+ else
+ {
+ // questa classe è derivata da Controller.Response... x cui recupero lo stream in altro modo...
+ string content = "";
+ System.Web.HttpContext.Current.Request.InputStream.Position = 0;
+ using (var reader = new StreamReader(Request.InputStream, System.Text.Encoding.UTF8, true, 4096, true))
+ {
+ content = reader.ReadToEnd();
+ }
+ //Rest
+ System.Web.HttpContext.Current.Request.InputStream.Position = 0;
+ // procedo a deserializzare in blocco l'oggetto...
+ List innovazioni = new List();
+ try
+ {
+ // deserializzo.
+ innovazioni = JsonConvert.DeserializeObject>(content);
+ // se != null --> salvo!
+ if (innovazioni != null)
+ {
+ // salvo
+ DataLayer DataLayerObj = new DataLayer();
+ DataLayerObj.upsertCurrObjItems(id, innovazioni);
+ answ = "OK";
+ }
+ }
+ catch (Exception exc)
+ {
+ logger.lg.scriviLog($"Errore in setCurrParams{Environment.NewLine}{exc}");
+ answ = "NO";
+ }
+ }
+ return answ;
+ }
+
+ #endregion Public Methods
+
+#endif
+
+ }
+}
diff --git a/GWMS.UI/Startup.cs b/GWMS.UI/Startup.cs
index 983ad9d..a1a3429 100644
--- a/GWMS.UI/Startup.cs
+++ b/GWMS.UI/Startup.cs
@@ -99,6 +99,7 @@ namespace GWMS.UI
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
endpoints.MapFallbackToPage("/_Host");
+
});
}
diff --git a/GWMS.sln b/GWMS.sln
index d2bc288..632e0ea 100644
--- a/GWMS.sln
+++ b/GWMS.sln
@@ -9,8 +9,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GWMS.UI", "GWMS.UI\GWMS.UI.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GWMS.User", "GWMS.User\GWMS.User.csproj", "{3112DC8B-4752-4D43-A9A7-41AC40A38DFD}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GWMS.API", "GWMS.API\GWMS.API.csproj", "{F455A3A5-89FD-49E3-B5B6-ED8B0DCEC444}"
-EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -29,10 +27,6 @@ Global
{3112DC8B-4752-4D43-A9A7-41AC40A38DFD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3112DC8B-4752-4D43-A9A7-41AC40A38DFD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3112DC8B-4752-4D43-A9A7-41AC40A38DFD}.Release|Any CPU.Build.0 = Release|Any CPU
- {F455A3A5-89FD-49E3-B5B6-ED8B0DCEC444}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {F455A3A5-89FD-49E3-B5B6-ED8B0DCEC444}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {F455A3A5-89FD-49E3-B5B6-ED8B0DCEC444}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {F455A3A5-89FD-49E3-B5B6-ED8B0DCEC444}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Resources/ChangeLog.html b/Resources/ChangeLog.html
index f42c9b1..7b41cb9 100644
--- a/Resources/ChangeLog.html
+++ b/Resources/ChangeLog.html
@@ -1,6 +1,6 @@
Modulo statistiche MAPO
- Versione: 1.0.2106.3010
+ Versione: 1.0.2107.2311
Note di rilascio:
diff --git a/Resources/VersNum.txt b/Resources/VersNum.txt
index aa04982..8f3aeca 100644
--- a/Resources/VersNum.txt
+++ b/Resources/VersNum.txt
@@ -1 +1 @@
-1.0.2106.3010
+1.0.2107.2311
diff --git a/Resources/manifest.xml b/Resources/manifest.xml
index ea1ead1..37a4907 100644
--- a/Resources/manifest.xml
+++ b/Resources/manifest.xml
@@ -1,6 +1,6 @@
-
- 1.0.2106.3010
+ 1.0.2107.2311
http://nexus.steamware.net/repository/SWS/MP-STATS/stable/0/GWMS.UI.zip
http://nexus.steamware.net/repository/SWS/MP-STATS/stable/0/ChangeLog.html
false