import ftplib import codecs import re import wimcollect.common.logger as logger def ftp_connect(ftp_config: dict, log: logger.Logger, object_id: str) -> ftplib.FTP: """Connect to FTP server according to parameters read from settings.ini file. Parameters ---------- ftp_config: dict Dict containing FTP connection information: `host`, `user` and `password`. log: wimcollect.logger Logger to record debug/info/error messages. object_id: str Object identifier, used as marker for log messages. Returns ------- ftplib.FTP FTP session. """ host = ftp_config["host"] user = ftp_config["user"] passwd = codecs.decode(ftp_config["password"], "rot_13") try: session = ftplib.FTP(host=host, user=user, passwd=passwd) except ftplib.error_perm as e: log.write(object_id, "Failed to connect to " + host + " as " + user + ": " + str(e)) raise except BlockingIOError as e: log.write(object_id, "Host [" + host + "] seems to be unreachable.") raise else: log.write(object_id, "Connected to [" + host + "] as user: " + user) return session def ftp_list_files(ftp_session: ftplib.FTP, distant_directory_path: str, log: logger.Logger, object_id: str) -> list: """List the files in the distant FTP directory. Parameters ---------- ftp_session: ftplib.FTP FTP session. distant_directory_path: str Full path of the directory whose content should be listed. log: wimcollect.logger Logger to record debug/info/error messages. object_id: str Object identifier, used as marker for log messages. Returns ------- list List of files in the FTP directory given in parameter. """ filepaths = [] try: filepaths = ftp_session.nlst(distant_directory_path) except ftplib.error_perm as resp: if str(resp) == "550 No files found": log.write(object_id, "No files in directory " + distant_directory_path) else: raise return filepaths def ftp_download_file(source_filepath: str, dest_filepath: str, log: logger.Logger, object_id: str, ftp_config: dict = None, ftp_session: ftplib.FTP = None, delete_if_success: bool = False) -> bool: """Download a file from the FTP server. Parameters ---------- source_filepath: str Full path of the distant to-be_downloaded file. dest_filepath: str Full path where the downloaded file should be stored. log: wimcollect.logger Logger to record debug/info/error messages. object_id: str Object identifier, used as marker for log messages. ftp_config: dict, optional Dict containing FTP connection information: `host`, `user` and `password`. If not set, `ftp_session` must be provided. ftp_session: ftplib.FTP, optional FTP session. If not set, a session will be created using `ftp_config` parameters. Specifying a custom ftp_session is useful when multiple tasks should be performed before/after downloading a single file. delete_if_success: bool, optional If `True`, the distant FTP file will be deleted from the server, once the transfer is successfully done. Returns ------- bool `True` if everything went well, otherwise raises Exception. """ # Open FTP session if missing if ftp_session is None: close_session = True ftp_session = ftp_connect(ftp_config, log, object_id) else: close_session = False # Try to download file log.write(object_id, "Downloading " + source_filepath + " ...") with open(dest_filepath, 'wb') as file: try: response = ftp_session.retrbinary('RETR %s' % source_filepath, file.write) except (ftplib.error_perm, ftplib.error_temp) as e: response = str(e) # Analyse FTP's response and emit messages accordingly response_code = int(re.sub("[^0-9]", "", response)) if response_code == 226: if delete_if_success: ftp_session.delete(source_filepath) log.write(object_id, "Done. Destination file: " + dest_filepath) else: log.write(object_id, "FAILED. Destination file: " + dest_filepath) raise Exception("Failed to download [" + source_filepath + "] in [" + dest_filepath + "]." " FTP response: [" + response + "]") # Close the FTP session if it has been opened at the beginning of this function. if close_session: ftp_session.quit() return True