using EgwProxy.Ftp; using FluentFTP; using IOB_UT_NEXT; using MapoSDK; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net.NetworkInformation; using System.Text; using System.Threading.Tasks; using static System.Windows.Forms.VisualStyles.VisualStyleElement; namespace IOB_WIN_NEXT.IobNet { /// /// Classe gestione sync via FTP /// public class Ftp : Iob.Generic { #region Public Constructors /// /// Estende l'init della classe base, impiegando il pacchetto EgwCoreLib.Ftp /// - gestione dei task da svolgere da configurazione json specifica /// - specializzazione da conf e non da codice /// /// /// public Ftp(AdapterForm caller, IobConfiguration IOBConf) : base(caller, IOBConf) { lgInfo("Init IobFtp Client"); // imposto B_input = 0; // init datetime counters DateTime adesso = DateTime.Now; lastPzCountSend = adesso; lastWarnODL = adesso; vetoCheckStatus = adesso; // 2023.09.05 imposto anche primo ping e check disconnected... lastPING = adesso; lastDisconnCheck = adesso; var VETO_PING_SEC = getOptPar("VETO_PING_SEC"); if (!string.IsNullOrEmpty(VETO_PING_SEC)) { int.TryParse(VETO_PING_SEC, out vetoPingSec); } var POWEROFF_TIMEOUT_SEC = getOptPar("POWEROFF_TIMEOUT_SEC"); if (!string.IsNullOrEmpty(POWEROFF_TIMEOUT_SEC)) { int.TryParse(POWEROFF_TIMEOUT_SEC, out PoweroffTimeoutSec); } // fix coda ping PingQueue = new DataQueue("000", "PingQueue", false); // carico conf specifica steps FTP string ftpConfFile = getOptPar("FTP_PARAM"); if (!string.IsNullOrEmpty(ftpConfFile)) { loadFtpConfFile(ftpConfFile); // mi calcolo ed imposto la ftpClient + Remote BaseDir... string actKey = "RemoteDir"; if (currFtpTaskList != null && currFtpTaskList.ListTask.Count > 0) { // prendo i task foreach (var fTask in currFtpTaskList.ListTask) { if (string.IsNullOrEmpty(RemoteBaseDir)) { foreach (var fAct in fTask.StepsList) { // cerco nei parametri... if (fAct.ParamList.ContainsKey(actKey)) { RemoteBaseDir = fAct.ParamList[actKey]; break; } } } if (ftpClient == null) { // setup ftpClient! ftpClient = new Manager(fTask.ServerAddr, fTask.ConnUser, fTask.ConnPasswd, fTask.RawCert, fTask.SkipCert); } } } } } /// /// Directpry remota di abse /// private string RemoteBaseDir { get; set; } = ""; #endregion Public Constructors #region Public Methods /// /// Processo i task richiesti e li elimino dalla coda /// /// public override Dictionary executeTasks(Dictionary task2exe) { /*--------------------------------------------------------------------------- * va creata una folder x ogni ODL (una volta LANCIATO da tablet) APERTO * - nella folder scriviamo un file con articolo, qta, commessa * - la folder sarà usata x salvare OGNI file necessario e di rilevazione * - per farlo si creeano degli ActionStep specifici e vengono poi richiamati... *---------------------------------------------------------------------------*/ // Verificare il protocollo: dovrebbe togliere SOLO i task eseguiti... Dictionary taskDone = new Dictionary(); ActionConfig currAct = new ActionConfig(); var currActParam = new Dictionary(); string actKey = "RemoteDir"; string newDir = ""; bool taskOk = false; string taskVal = ""; // cerco task specifici: se ho startSetup --> imposto bit DBB701.DBB0.4 foreach (var item in task2exe) { taskOk = false; taskVal = ""; // converto richiesta in enum... taskType tName = taskType.nihil; Enum.TryParse(item.Key, out tName); // controllo sulla KEY... switch (tName) { case taskType.setComm: /*------------------------------------------------------------------ * La commessa è la cartella DI BASE per poter poi procedere con acquisizione dati... * - step 1: check folder * - step 2: creazione folder ------------------------------------------------------------------*/ // compongo remDir dai 2 parametri... newDir = $"{RemoteBaseDir}/{item.Value}"; currActParam.Add(actKey, newDir); currAct = new ActionConfig() { Id = "01", Description = "Verifica esistenza directory", Action = ActType.CheckDir, ParamList = currActParam }; //eseguo step... bool dirOk = doStep(currAct); // se la cartella mancasse if (!dirOk) { // nuovo act x crearla! currAct = new ActionConfig() { Id = "02", Description = "Creazione directory", Action = ActType.CreateDir, ParamList = currActParam }; //eseguo step... dirOk = doStep(currAct); if (dirOk) { taskVal = $"DIR Created: {item.Key} --> {item.Value}"; } } else { taskVal = $"DIR Already Exists: {item.Key} --> {item.Value}"; } // salvo in currProd.. saveProdData(new KeyValuePair(item.Key, item.Value)); break; case taskType.setArt: /*------------------------------------------------------------------ * Articolo è un file nella cartella commessa di info accessorie... * - step 1: check info folder presente in prod data * - step 2: creazione file info con articolo e commessa ------------------------------------------------------------------*/ #if false // compongo remDir dai 2 parametri... newDir = $"{remDir}/{item.Value}"; currParam.Add(actKey, newDir); currAct = new ActionConfig() { Id = "01", Description = "Verifica esistenza directory", Action = ActType.CheckDir, ParamList = currParam }; //eseguo step... bool dirOk = doStep(currAct); // se la cartella mancasse if (!dirOk) { // nuovo act x crearla! currAct = new ActionConfig() { Id = "02", Description = "Creazione directory", Action = ActType.CreateDir, ParamList = currParam }; //eseguo step... dirOk = doStep(currAct); if (dirOk) { taskVal = $"DIR Created: {item.Key} --> {item.Value}"; } } else { taskVal = $"DIR Already Exists: {item.Key} --> {item.Value}"; } // salvo in currProd.. saveProdData(new KeyValuePair(item.Key, item.Value)); #endif break; default: //taskVal = $"taskReq: {tName} | key: {item.Key} | val: {item.Value} | SKIPPED | NO EXEC"; //lgInfo($"Chiamata senza processing: taskOk: {taskOk} | taskVal: {taskVal}"); break; } // aggiungo task SE svolto! if (!string.IsNullOrEmpty(taskVal)) { taskDone.Add(item.Key, taskVal); } } return taskDone; } /// /// Effettua processing CUSTOM x FTP: /// - prende ogni conf specifica /// - esegue step /// - registra eventuali DynData da salvare /// public override void processCustomTaskLF() { lgInfo($"Richiesto processCustomTaskLF"); // verifico di avere compiti da svolgere... if (currFtpTaskList != null && currFtpTaskList.ListTask != null && currFtpTaskList.ListTask.Count > 0) { string sVal = ""; foreach (var srvFtp in currFtpTaskList.ListTask) { // verifico eventuale veto all'esecuzione... if (ActionEnabled(srvFtp)) { // imposto nuovo veto... ActionResetVeto(srvFtp); // ora setup server FTP x item... ftpClient = new Manager(srvFtp.ServerAddr, srvFtp.ConnUser, srvFtp.ConnPasswd, srvFtp.RawCert, srvFtp.SkipCert); // test server ok... if (ftpClient.ServerOk()) { int stepDone = 0; string srvType = ftpClient.ServerType(); lgTrace($"Connesso a server {srvType} | {srvFtp.ServerAddr} | inizio processing {srvFtp.StepsList.Count} steps"); // ciclo tra i vari steps! foreach (var step in srvFtp.StepsList) { bool fatto = doStep(step); stepDone += fatto ? 1 : 0; } lgInfo($"Completata esecuzione steps FTP | {srvFtp.ActionId} | {stepDone}/{srvFtp.StepsList.Count} Done/Req"); } else { //connectionOk = false; //tryDisconnect(); lgError($"Impossibile connettersi al server {srvFtp.ServerAddr}"); } } else { lgTrace($"Saltata esecuzione {srvFtp.ActionId} | veto attivo"); } } } lastReadPLC = DateTime.Now; } /// /// Effettua lettura semafori principale Parametri da /// aggiornare x display in form /// public override void readSemafori(ref newDisplayData currDispData) { DateTime adesso = DateTime.Now; // salto se fosse attivo il veto ping... if (lastPING.AddSeconds(vetoPingSec) < adesso) { // lo stato è come ping machine, x ora puntato a IP unico (WiFi?) byte[] MemBlock = new byte[2]; try { currDispData.semIn = Semaforo.SV; // in primis salvo data ping comunque... lastPING = adesso; // salvo esito ping bool pingOK = testPingMachine == IPStatus.Success; addTest(pingOK); // se passa il ping faccio il resto... if (pingStatusOk()) { connectionOk = true; lastReadPLC = adesso; lastWatchDog = adesso; } else { connectionOk = false; } if (connectionOk) { //B_input = 1; B_input = 3; } else { B_input = 0; } // aggiungo NON emergenza... B_input += (1 << 7); #if false // annullo lettura bit signal IN pre/post x evitare invio automatico... B_output = B_input; B_previous = B_input; #endif } catch { currDispData.semIn = Semaforo.SR; } } } public override void startAdapter(bool resetQueue) { base.startAdapter(resetQueue); // 2023.09.05 imposto anche primo ping e check disconnected... DateTime adesso = DateTime.Now; lastWatchDog = adesso; //lastPING = adesso; lastReadPLC = adesso; lastDisconnCheck = adesso; // faccio un primo check POST ritardo tryConnect(); } /// /// Override connessione /// public override void tryConnect() { bool doLog = (verboseLog || periodicLog); lgDebug($"FTP: tryConnect step 01 | connectionOk: {connectionOk}"); if (!connectionOk) { //// resetto coda... //PingQueue = new DataQueue("000", "PingQueue", false); // controllo che il ping sia stato tentato almeno pingTestSec fa... if (DateTime.Now.Subtract(lastPING).TotalSeconds > utils.CRI("pingTestSec")) { if (doLog) { lgInfo("FTP: ConnKO - tryConnect"); } lgDebug("FTP: tryConnect step 04"); lgDebug("FTP: Reset PingQueue"); bool pingOK = testPingMachine == IPStatus.Success; addTest(pingOK); // se passa il ping faccio il resto... if (pingStatusOk()) { // in primis salvo data ping... lastPING = DateTime.Now; connectionOk = true; queueInEnabCurr = true; lgInfo("FTP OK"); } else { // loggo no risposta ping ... lgError("FTP KO"); } } } } /// /// Override disconnessione /// public override void tryDisconnect() { lgInfo("Richiesta disconnessione adapter FTP!"); connectionOk = false; queueInEnabCurr = false; } #endregion Public Methods #region Private Fields private static Stopwatch sw = new Stopwatch(); /// /// Dimensione coda di ping x valutazione /// private int maxQueuePing = 11; /// /// Coda degli esiti di ping x calcolo stato macchina /// private DataQueue PingQueue = new DataQueue("000", "PingQueue", false); private int PoweroffTimeoutSec = 100; /// /// Dizionario dei divieti di esecuzione x i vari step /// private Dictionary StepsVeto = new Dictionary(); /// /// Veto controllo status x log... /// private DateTime vetoCheckStatus = DateTime.Now; #endregion Private Fields #region Private Properties /// /// Oggetto configurazione gestione FTP /// private FtpTaskList currFtpTaskList { get; set; } = new FtpTaskList(); /// /// CLient connessioni FTP /// private Manager ftpClient { get; set; } = new Manager("", "", "", "", false); #endregion Private Properties #region Private Methods /// /// Verifica se l'azione sia permessa o in stato veto a tempo /// /// /// private bool ActionEnabled(FtpActConf currAct) { bool enabled = true; // se veto presente if (StepsVeto.ContainsKey(currAct.ActionId)) { // controllo scadenza enabled = StepsVeto[currAct.ActionId] < DateTime.Now; } return enabled; } /// /// Imposta veto azione corrente /// /// /// private bool ActionResetVeto(FtpActConf currAct) { bool fatto = false; if (StepsVeto.ContainsKey(currAct.ActionId)) { StepsVeto[currAct.ActionId] = DateTime.Now.AddSeconds(currAct.ReExecVeto); } else { StepsVeto.Add(currAct.ActionId, DateTime.Now.AddSeconds(currAct.ReExecVeto)); } return fatto; } private void addTest(bool pingOk) { int score = pingOk ? 1 : 0; // controllo: se era spenta e risulta ping ok --> reset coda! if (B_input == 0 && pingOk) { B_input = 1; PingQueue = new DataQueue("000", "PingQueue", false); lgTrace($"PingQueue resetted on addTest"); } PingQueue.Enqueue($"{score}"); while (PingQueue.Count > maxQueuePing) { string res = ""; PingQueue.TryDequeue(out res); } } /// /// Esegue l'azione configurata /// /// /// /// private bool doStep(ActionConfig step) { bool fatto = false; string remoteDir = ""; string localDir = ""; string sVal = ""; string actKey = ""; string actVal = ""; // faccio switch in base al tipo di azione da eseguire... switch (step.Action) { case ActType.CheckDir: if (step.ParamList != null && step.ParamList.Count > 0) { sw.Restart(); remoteDir = step.ParamList["RemoteDir"] ?? ""; //verifico dir remota fatto = ftpClient.DirExists(remoteDir); } else { lgError("Error: missing parameters!"); } sw.Stop(); if (fatto) { lgInfo($"Check RemDir: {remoteDir} | {sw.ElapsedMilliseconds:N1} ms"); } break; case ActType.CheckFile: break; case ActType.CreateDir: if (step.ParamList != null && step.ParamList.Count > 0) { sw.Restart(); remoteDir = step.ParamList["RemoteDir"] ?? ""; //verifico dir remota bool dirExist = ftpClient.DirExists(remoteDir); if (dirExist) { lgTrace("Error: Folder already exists!"); } else { fatto = ftpClient.CreateDir(remoteDir); } } else { lgError("Error: missing parameters!"); } sw.Stop(); if (fatto) { lgInfo($"Directory {remoteDir} created!| {sw.ElapsedMilliseconds:N1} ms"); } break; case ActType.DelDir: break; case ActType.DelFile: break; case ActType.DownloadDir: break; case ActType.DownloadFile: break; case ActType.GenRandomDir: break; case ActType.ListContent: break; case ActType.MirrorDirL2R: break; case ActType.MirrorDirR2L: // eseguo mirroring directory actKey = "FtpSync"; actVal = "SRC --> DEST | Size"; if (step.ParamList != null && step.ParamList.Count > 1) { sw.Restart(); remoteDir = step.ParamList["RemoteDir"] ?? ""; localDir = step.ParamList["LocalDir"] ?? ""; // verifico esistenza dir locale... if (!Directory.Exists(localDir)) { Directory.CreateDirectory(localDir); } //verifico dir remota var preTest = ftpClient.DirExists(remoteDir); if (preTest) { // chiamo metodo MIRROR x calcolare esattamente se ci siano stati // download di sync... var mirResult = ftpClient.MirrorRemoteDir(localDir, remoteDir, FluentFTP.FtpFolderSyncMode.Mirror); // ciclo cercando eventuali info da emttere in DynData... foreach (var result in mirResult) { string objSize = MeasureUtils.SizeSuffix(result.Size, 3); actVal = $"Rem2Loc | {result.Name} | {objSize} | {result.RemotePath}"; sVal = $"{actKey} | {actVal}"; if (result.IsDownload && result.IsSuccess && !result.IsSkipped) { accodaFLog(sVal, qEncodeFLog(actKey, actVal)); } else { lgTrace($"Skipped sync | {actVal}"); } } // risultato sintetico come successi... fatto = mirResult != null && mirResult.Where(x => !x.IsSuccess).Count() == 0; if (!fatto) { lgError($"Error: {remoteDir} NOT mirrored!"); } } else { lgError($"Dir remota non trovata! RemDir: {remoteDir}"); } } else { lgError("Error: missing parameters!"); } sw.Stop(); if (fatto) { lgInfo($"Mirror Rem2Loc | RemDir: {remoteDir} | {sw.ElapsedMilliseconds:N1} ms"); } break; case ActType.PingServer: break; case ActType.UploadDir: break; case ActType.UploadFile: break; default: break; } return fatto; } /// /// Effettuo lettura file di conf /// /// private void loadFtpConfFile(string fileName) { string jsonFullPath = Path.Combine(System.Windows.Forms.Application.StartupPath, "DATA", "CONF", fileName); lgInfo($"Apertura file {jsonFullPath}"); using (StreamReader reader = new StreamReader(jsonFullPath)) { string jsonData = reader.ReadToEnd().Replace("\n", "").Replace("\r", ""); if (!string.IsNullOrEmpty(jsonData)) { lgDebug($"File json composto da {jsonData.Length} caratteri"); try { currFtpTaskList = JsonConvert.DeserializeObject(jsonData); lgDebug($"Decodifica aree FtpTaskList: trovati {currFtpTaskList.ListTask.Count} gruppi di task FTP"); } catch (Exception exc) { lgError($"Eccezione in decodifica conf json FTP:{Environment.NewLine}{exc}"); } } else { lgError("Errore in loadFtpConfFile: file json vuoto!"); } } } /// /// Calcola status ping: /// - se ha ‹ 50% coda richiesta --› true /// - se ha › 50% coda richiesta --› true se è maggior parte a 1 (true) /// /// private bool pingStatusOk() { bool answ = true; int numVal = PingQueue.Count; if (numVal > maxQueuePing / 2) { var listaValori = PingQueue.ToList(); int numOk = listaValori.Where(x => x == "1").Count(); int numKo = numVal - numOk; answ = numOk >= numKo; lgTrace($"PING ok per: {numOk} > {numKo}"); } else { lgTrace("PING ok per mancanza dati minimi test"); } return answ; } #endregion Private Methods } }