from ftplib import FTP, FTP_TLS, all_errors from io import BytesIO import logging import aiomysql logger = logging.getLogger(__name__) class FTPConnection: """ Manages an FTP or FTP_TLS connection, providing a context manager for automatic disconnection. """ def __init__(self, host, port=21, use_tls=False, user='', passwd='', passive=True, timeout=None, debug=0, context=None): self.use_tls = use_tls if use_tls: self.ftp = FTP_TLS(context=context, timeout=timeout) if context else FTP_TLS(timeout=timeout) else: self.ftp = FTP(timeout=timeout) if debug > 0: self.ftp.set_debuglevel(debug) self.ftp.connect(host, port) self.ftp.login(user, passwd) self.ftp.set_pasv(passive) if use_tls: self.ftp.prot_p() def __getattr__(self, name): """Delega tutti i metodi non definiti all'oggetto FTP sottostante""" return getattr(self.ftp, name) def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.ftp.quit() async def send_raw_csv_to_customer(cfg: dict, id: int, unit: str, tool: str, csv_data: str, pool: object) -> bool: None return True async def send_elab_csv_to_customer(cfg: dict, id: int, unit: str, tool: str, csv_data: str, pool: object) -> bool: """ Sends elaborated CSV data to a customer via FTP. Retrieves FTP connection details from the database based on the unit name, then establishes an FTP connection and uploads the CSV data. Args: cfg (dict): Configuration dictionary (not directly used in this function but passed for consistency). id (int): The ID of the record being processed (used for logging). unit (str): The name of the unit associated with the data. tool (str): The name of the tool associated with the data. csv_data (str): The CSV data as a string to be sent. pool (object): The database connection pool. Returns: bool: True if the CSV data was sent successfully, False otherwise. """ query = """ select ftp_addrs, ftp_user, ftp_passwd, ftp_parm, ftp_filename, ftp_target, duedate from units where name = '%s'";' """ async with pool.acquire() as conn: async with conn.cursor(aiomysql.DictCursor) as cur: try: await cur.execute(query, (unit,)) send_ftp_info = await cur.fetchone() logger.info(f"id {id} - {unit} - {tool}: estratti i dati per invio via ftp") except Exception as e: logger.error(f"id {id} - {unit} - {tool} - errore nel query per invio ftp: {e}") try: # Converti in bytes csv_bytes = csv_data.encode('utf-8') csv_buffer = BytesIO(csv_bytes) ftp_parms = parse_ftp_parms(send_ftp_info["ftp_parm"]) use_tls = 'ssl_version' in ftp_parms passive = ftp_parms.get('passive', True) port = ftp_parms.get('port', 21) # Connessione FTP with FTPConnection(host=send_ftp_info["ftp_addrs"], port=port, use_tls=use_tls, user=send_ftp_info["ftp_user"], passwd=send_ftp_info["ftp_passwd"], passive=passive) as ftp: # Cambia directory if send_ftp_info["ftp_target"] != "/": ftp.cwd(send_ftp_info["ftp_target"]) # Invia il file result = ftp.storbinary(f'STOR {send_ftp_info["ftp_filename"]}', csv_buffer) if result.startswith('226'): logger.info(f"File {send_ftp_info["ftp_filename"]} inviato con successo") return True else: logger.error(f"Errore nell'invio: {result}") return False except all_errors as e: logger.error(f"Errore FTP: {e}") return False except Exception as e: logger.error(f"Errore generico: {e}") return False finally: csv_buffer.close() def parse_ftp_parms(ftp_parms): """ Parses a string of FTP parameters into a dictionary. Args: ftp_parms (str): A string containing key-value pairs separated by commas, with keys and values separated by '=>'. Returns: dict: A dictionary where keys are parameter names (lowercase) and values are their parsed values. """ # Rimuovere spazi e dividere per virgola pairs = ftp_parms.split(',') result = {} for pair in pairs: if '=>' in pair: key, value = pair.split('=>', 1) key = key.strip().lower() value = value.strip().lower() # Convertire i valori appropriati if value.isdigit(): value = int(value) elif value == '': value = None result[key] = value return result