Files
Mapo-IOB-WIN/IOB-WIN-NEXT/IobNet/Ftp.cs
T
Samuele Locatelli 231bf245ae EgwProxy.FTP
- aggiunta eliminazione elenco file in singolo step connessione
2024-10-08 16:16:29 +02:00

825 lines
32 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 OpenQA.Selenium.Remote;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Packaging;
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");
// 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.IsConfigured)
{
// setup ftpClient!
ftpClient = new Manager(fTask.ServerAddr, fTask.ConnUser, fTask.ConnPasswd, fTask.RawCert, fTask.SkipCert);
}
}
}
}
}
#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 = "";
string fNameOdl = "ODL_ATTIVO.txt";
// 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.startSetup:
case taskType.stopSetup:
// verifico di avere già un ODL corrente...
if (currIdxODL == 0)
{
lgTrace($"Manca ODL corrente: non procedo con impostazione start/stop setup");
}
else
{
string odlFolder = $"ODL{currIdxODL:00000000}";
// scrivo file ultima richiesta setup
string fName = $"{item.Key}.txt";
string basePath = System.Windows.Forms.Application.StartupPath;
string tempDir = Path.Combine(basePath, "temp");
string locFile = Path.Combine(tempDir, fName);
// salva col nome FISSO di ODL_CORRENTE
string remFile = $"{RemoteBaseDir}/{odlFolder}/{fNameOdl}";
//string remFile = $"{RemoteBaseDir}/{odlFolder}/{fName}";
// creo file locale
File.WriteAllText(locFile, item.Value);
// invio file x ODL attivo
currActParam.Add("LocalFile", locFile);
currActParam.Add("RemoteFile", remFile);
currAct = new ActionConfig()
{
Id = "01",
Description = "Upload File",
Action = ActType.UploadFile,
ParamList = currActParam
};
// eseguo step...
taskOk = doStep(currAct);
if (taskOk)
{
taskVal = $"File uploaded: {item.Key} --> {item.Value} | locFile: {locFile} | remFile: {remFile}";
}
else
{
lgError($"Error in startSetup | fileUpload | locFile: {locFile} | remFile: {remFile}");
}
}
break;
case taskType.endProd:
currActParam.Add("RemoteDir", RemoteBaseDir);
currActParam.Add("FileName2Del", fNameOdl);
currAct = new ActionConfig()
{
Id = "01",
Description = "Clean Curr ODL Files",
Action = ActType.RemoveFileByName,
ParamList = currActParam
};
// eseguo step...
taskOk = doStep(currAct);
if (taskOk)
{
taskVal = $"Cleaned CurrOdlFile: {item.Key} --> {item.Value} | remDir: {RemoteBaseDir} | fName: {fNameOdl}";
}
else
{
lgError($"Error in CurrOdlFile | remDir: {RemoteBaseDir} | fName: {fNameOdl}");
}
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;
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();
}
/// <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);
/// <summary>
/// Directpry remota di abse
/// </summary>
private string RemoteBaseDir { get; set; } = "";
#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;
string remoteVal = "";
string localVal = "";
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();
remoteVal = step.ParamList["RemoteDir"] ?? "";
//verifico dir remota
fatto = ftpClient.DirExists(remoteVal);
}
else
{
lgError("Error: missing parameters!");
}
sw.Stop();
if (fatto)
{
lgInfo($"Check RemDir: {remoteVal} | {sw.ElapsedMilliseconds:N1} ms");
}
break;
case ActType.CheckFile:
break;
case ActType.CreateDir:
if (step.ParamList != null && step.ParamList.Count > 0)
{
sw.Restart();
remoteVal = step.ParamList["RemoteDir"] ?? "";
//verifico dir remota
bool dirExist = ftpClient.DirExists(remoteVal);
if (dirExist)
{
lgTrace("Error: Folder already exists!");
}
else
{
fatto = ftpClient.CreateDir(remoteVal);
}
}
else
{
lgError("Error: missing parameters!");
}
sw.Stop();
if (fatto)
{
lgInfo($"Directory {remoteVal} 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();
remoteVal = step.ParamList["RemoteDir"] ?? "";
localVal = step.ParamList["LocalDir"] ?? "";
// verifico esistenza dir locale...
if (!Directory.Exists(localVal))
{
Directory.CreateDirectory(localVal);
}
//verifico dir remota
var preTest = ftpClient.DirExists(remoteVal);
if (preTest)
{
// chiamo metodo MIRROR x calcolare esattamente se ci siano stati
// download di sync...
var mirResult = ftpClient.MirrorRemoteDir(localVal, remoteVal, 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: {remoteVal} NOT mirrored!");
}
}
else
{
lgError($"Dir remota non trovata! RemDir: {remoteVal}");
}
}
else
{
lgError("Error: missing parameters!");
}
sw.Stop();
if (fatto)
{
lgInfo($"Mirror Rem2Loc | RemDir: {remoteVal} | {sw.ElapsedMilliseconds:N1} ms");
}
break;
case ActType.PingServer:
break;
case ActType.RemoveFileByName:
string fName2Del = "";
int numRem = 0;
if (step.ParamList != null && step.ParamList.Count > 1)
{
sw.Restart();
remoteVal = step.ParamList["RemoteDir"] ?? "";
fName2Del = step.ParamList["FileName2Del"] ?? "";
//verifico dir remota
fatto = ftpClient.DirExists(remoteVal);
if (fatto)
{
// recupero elenco di TUTTI i file presenti
List<FluentFTP.FtpListItem> resList = ftpClient.GetRemoteList(remoteVal, true);
List<FluentFTP.FtpListItem> list2del = new List<FluentFTP.FtpListItem>();
// cerco tutti i file che indicano ODL attivo e li elimino
foreach (var flItem in resList)
{
if (flItem.Type == FluentFTP.FtpObjectType.File && flItem.Name.EndsWith(fName2Del))
{
list2del.Add(flItem);
}
}
// ciclo sui file da eliminare...
foreach (var file2del in list2del)
{
bool delOk= ftpClient.DeleteFile(file2del.FullName);
numRem += delOk ? 1 : 0;
}
}
}
else
{
lgError("Error: missing parameters!");
}
sw.Stop();
if (fatto)
{
lgInfo($"RemoveFileByName | path: {remoteVal} | name: {fName2Del} | # del: {numRem} | {sw.ElapsedMilliseconds:N1} ms");
}
break;
case ActType.UploadDir:
break;
case ActType.UploadFile:
if (step.ParamList != null && step.ParamList.Count > 1)
{
sw.Restart();
localVal = step.ParamList["LocalFile"] ?? "";
remoteVal = step.ParamList["RemoteFile"] ?? "";
fatto = ftpClient.SendFile(localVal, remoteVal);
if (!fatto)
{
lgError($"Error: {localVal} NOT uploaded!");
}
}
else
{
lgError("Error: missing parameters!");
}
sw.Stop();
if (fatto)
{
lgInfo($"Upload File: {remoteVal} | {sw.ElapsedMilliseconds:N1} ms");
}
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
}
}