Files
Mapo-IOB-WIN/IOB-WIN-NEXT/IobNet/Ftp.cs
T
Samuele Locatelli 76fc82705e FTP:
- inizio gestione ODL (da provare con cambio su tablet)
- fluxlog delle azioni creazione DIR su FTP
2024-10-07 20:06:07 +02:00

685 lines
26 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
{
/// <summary>
/// Classe gestione sync via FTP
/// </summary>
public class Ftp : Iob.Generic
{
#region Public Constructors
/// <summary>
/// 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
/// </summary>
/// <param name="caller"></param>
/// <param name="IOBConf"></param>
public Ftp(AdapterForm caller, IobConfiguration IOBConf) : base(caller, IOBConf)
{
lgInfo("Init IobFtp Client");
// 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 remoteBaseDir...
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;
}
}
}
}
}
}
}
/// <summary>
/// Directpry remota di abse
/// </summary>
private string RemoteBaseDir { get; set; } = "";
#endregion Public Constructors
#region Public Methods
/// <summary>
/// Processo i task richiesti e li elimino dalla coda
/// </summary>
/// <param name="task2exe"></param>
public override Dictionary<string, string> executeTasks(Dictionary<string, string> 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<string, string> taskDone = new Dictionary<string, string>();
ActionConfig currAct = new ActionConfig();
var currActParam = new Dictionary<string, string>();
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<string, string>(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<string, string>(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;
}
/// <summary>
/// Effettua processing CUSTOM x FTP:
/// - prende ogni conf specifica
/// - esegue step
/// - registra eventuali DynData da salvare
/// </summary>
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;
}
/// <summary>
/// Effettua lettura semafori principale <paramref name="currDispData">Parametri da
/// aggiornare x display in form</paramref>
/// </summary>
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;
}
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();
}
/// <summary>
/// Override connessione
/// </summary>
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");
}
}
}
}
/// <summary>
/// Override disconnessione
/// </summary>
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();
/// <summary>
/// Dimensione coda di ping x valutazione
/// </summary>
private int maxQueuePing = 11;
/// <summary>
/// Coda degli esiti di ping x calcolo stato macchina
/// </summary>
private DataQueue PingQueue = new DataQueue("000", "PingQueue", false);
private int PoweroffTimeoutSec = 100;
/// <summary>
/// Dizionario dei divieti di esecuzione x i vari step
/// </summary>
private Dictionary<string, DateTime> StepsVeto = new Dictionary<string, DateTime>();
/// <summary>
/// Veto controllo status x log...
/// </summary>
private DateTime vetoCheckStatus = DateTime.Now;
#endregion Private Fields
#region Private Properties
/// <summary>
/// Oggetto configurazione gestione FTP
/// </summary>
private FtpTaskList currFtpTaskList { get; set; } = new FtpTaskList();
/// <summary>
/// CLient connessioni FTP
/// </summary>
private Manager ftpClient { get; set; } = new Manager("", "", "", "", false);
#endregion Private Properties
#region Private Methods
/// <summary>
/// Verifica se l'azione sia permessa o in stato veto a tempo
/// </summary>
/// <param name="currAct"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Imposta veto azione corrente
/// </summary>
/// <param name="currAct"></param>
/// <returns></returns>
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);
}
}
/// <summary>
/// Esegue l'azione configurata
/// </summary>
/// <param name="step"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
private bool doStep(ActionConfig step)
{
bool fatto = false;
// faccio switch in base al tipo di azione da eseguire...
switch (step.Action)
{
case ActType.CheckDir:
break;
case ActType.CheckFile:
break;
case ActType.CreateDir:
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
string remoteDir = "";
string localDir = "";
string sVal = "";
string actKey = "FtpSync";
string 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;
}
/// <summary>
/// Effettuo lettura file di conf
/// </summary>
/// <param name="fileName"></param>
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<FtpTaskList>(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!");
}
}
}
/// <summary>
/// Calcola status ping:
/// - se ha 50% coda richiesta -- true
/// - se ha 50% coda richiesta -- true se è maggior parte a 1 (true)
/// </summary>
/// <returns></returns>
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
}
}