diff --git a/zxdb.py b/zxdb.py index f57ecd5..1654d76 100644 --- a/zxdb.py +++ b/zxdb.py @@ -22,7 +22,7 @@ from urllib3.util.retry import Retry logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') # Configuración de base de datos -config_db = { +CONFIG_DB = { "user": config.DB_USER, "password": config.DB_PASSWORD, "host": config.DB_HOST, @@ -32,32 +32,32 @@ config_db = { } # Direcciones de internet de donde descargar los datos -url_prefix = { +URL_PREFIX = { "spectrum_computing": r"https://spectrumcomputing.co.uk", "wos": r"https://php.sustancia.synology.me/wos", "nvg": r"https://php.sustancia.synology.me/nvg", } # Rutas locales donde depositar los resultados -destination_path = config.DESTINATION_PATH -cache_path = config.CACHE_PATH -temp_file = config.TEMP_FILE +DESTINATION_PATH = config.DESTINATION_PATH +CACHE_PATH = config.CACHE_PATH +TEMP_FILE = config.TEMP_FILE # Parámetros de configuración -should_clear_destination_path = config.SHOULD_CLEAR_DESTINATION_PATH # Establece si se limpia primero la carpeta de destino -should_split_modern_and_classic = config.SHOULD_SPLIT_MODERN_AND_CLASSIC # Separa los juegos en dos carpetas a partir de un año especificado -should_sort_by_year = config.SHOULD_SORT_BY_YEAR # Separa los juegos por carpetas en función de su año de lanzamiento -should_sort_by_letter = config.SHOULD_SORT_BY_LETTER # Separa los juegos por carpetas en función de su primera letra -should_sort_by_developer = config.SHOULD_SORT_BY_DEVELOPER # Separa los juegos por desarrollador -wait = config.WAIT # Establece una pausa aleatoria entre descargas +SHOULD_CLEAR_DESTINATION_PATH = config.SHOULD_CLEAR_DESTINATION_PATH # Establece si se limpia primero la carpeta de destino +SHOULD_SPLIT_MODERN_AND_CLASSIC = config.SHOULD_SPLIT_MODERN_AND_CLASSIC # Separa los juegos en dos carpetas a partir de un año especificado +SHOULD_SORT_BY_YEAR = config.SHOULD_SORT_BY_YEAR # Separa los juegos por carpetas en función de su año de lanzamiento +SHOULD_SORT_BY_LETTER = config.SHOULD_SORT_BY_LETTER # Separa los juegos por carpetas en función de su primera letra +SHOULD_SORT_BY_DEVELOPER = config.SHOULD_SORT_BY_DEVELOPER # Separa los juegos por desarrollador +WAIT = config.WAIT # Establece una pausa aleatoria entre descargas # Leer variables numéricas -min_wait = config.MIN_WAIT # Cantidad de segundos mínima a esperar entre descargas -max_wait = config.MAX_WAIT # Cantidad de segundos máxima a esperar entre descargas -last_classic_year = config.LAST_CLASSIC_YEAR # Año usado para la separación entre juegos clásicos y modernos +MIN_WAIT = config.MIN_WAIT # Cantidad de segundos mínima a esperar entre descargas +MAX_WAIT = config.MAX_WAIT # Cantidad de segundos máxima a esperar entre descargas +LAST_CLASSIC_YEAR = config.LAST_CLASSIC_YEAR # Año usado para la separación entre juegos clásicos y modernos # Tipos de fichero que se guardan en la carpeta raíz del juego -filetypes_on_root = [ +FILETYPES_ON_ROOT = [ "Tape image", "Disk image", "Snapshot image", @@ -65,7 +65,7 @@ filetypes_on_root = [ ] # Resto de variables globales -elements = [] +ELEMENTS = [] # Carga un fichero con consultas SQL def load_queries(file_path): @@ -74,12 +74,29 @@ def load_queries(file_path): return [query.strip() for query in queries if query.strip()] # Carga las consultas desde el archivo -queries = load_queries('queries.sql') +QUERIES = load_queries('queries.sql') -# Listado con las consultas def select(cursor, query_index=0): + """ + Ejecuta la consulta seleccionada y procesa los resultados. + + Parámetros: + cursor (MySQLCursor): El cursor de la base de datos para ejecutar la consulta. + query_index (int): El índice de la consulta a ejecutar en la lista de consultas QUERIES (predeterminado es 0). + + Comportamiento: + - Ejecuta la consulta indicada por `query_index` usando el cursor proporcionado. + - Procesa los resultados de la consulta y los guarda en la lista ELEMENTS. + - Cada fila de resultados se convierte en un diccionario con las claves 'title', 'developer', 'release_year', 'url', y 'filetype'. + - Registra un mensaje informativo indicando que la consulta se ejecutó correctamente y el número de resultados obtenidos. + + Ejemplo: + >>> cursor = connection.cursor() + >>> select(cursor, query_index=1) + """ + # Ejecutar la consulta seleccionada - cursor.execute(queries[query_index]) + cursor.execute(QUERIES[query_index]) # Procesar los resultados for row in cursor: @@ -90,15 +107,34 @@ def select(cursor, query_index=0): "url": row[3], "filetype": row[4], } - elements.append(element) + ELEMENTS.append(element) # Registro de consulta ejecutada - logging.info(f"Consulta {query_index} ejecutada correctamente con {len(elements)} resultados.") + logging.info(f"Consulta {query_index} ejecutada correctamente con {len(ELEMENTS)} resultados.") + -# Establece la conexión a la BBDD y ejecuta la consulta def connect(query_index=0): + """ + Establece la conexión a la base de datos y ejecuta la consulta. + + Parámetros: + query_index (int): Índice de la consulta a ejecutar (predeterminado es 0). + + Excepciones: + mysql.connector.Error: Captura y maneja errores específicos de MySQL. + Exception: Captura y maneja cualquier otro error inesperado. + + Comportamiento: + - Conecta a la base de datos usando los parámetros de configuración en CONFIG_DB. + - Ejecuta la consulta definida por la función select utilizando el cursor. + - Maneja errores de acceso denegado y base de datos no encontrada, así como cualquier otro error inesperado. + + Ejemplo: + >>> connect(1) + """ + try: - with mysql.connector.connect(**config_db) as connection: + with mysql.connector.connect(**CONFIG_DB) as connection: with connection.cursor() as cursor: # Ejecutar la consulta 1 select(cursor, query_index=query_index) @@ -112,8 +148,25 @@ def connect(query_index=0): except Exception as e: logging.error(f"Error inesperado: {e}") -# Añade un prefijo a la url + def update_url(element, url_prefix): + """ + Añade un prefijo a la URL en el diccionario `element` según el prefijo proporcionado en `url_prefix`. + + Parámetros: + element (dict): Diccionario que contiene la clave 'url'. + url_prefix (dict): Diccionario que contiene los prefijos de las URLs para 'spectrum_computing', 'wos' y 'nvg'. + + Modifica: + element (dict): Actualiza el valor de 'url' en el diccionario `element` con el prefijo correspondiente. + + Ejemplo: + >>> element = {"url": "/zxdb/example"} + >>> url_prefix = {"spectrum_computing": "https://spectrumcomputing.co.uk", "wos": "https://php.sustancia.synology.me/wos", "nvg": "https://php.sustancia.synology.me/nvg"} + >>> update_url(element, url_prefix) + >>> print(element["url"]) + https://spectrumcomputing.co.uk/zxdb/example + """ url = element["url"] if url.startswith("/zxdb"): element["url"] = url_prefix["spectrum_computing"] + url @@ -122,41 +175,96 @@ def update_url(element, url_prefix): elif url.startswith("/nvg"): element["url"] = url_prefix["nvg"] + url[4:] -# Procesa todos lo elementos, modificando cada uno de sus parametros + def process_elements(): - global elements - for i in range(len(elements)): + """ + Procesa todos los elementos, modificando cada uno de sus parámetros. + + Comportamiento: + - Construye el nombre de la carpeta raíz basado en el título y año de lanzamiento, o título, año de lanzamiento y desarrollador. + - Normaliza el nombre de la carpeta raíz. + - Añade el prefijo a la URL y normaliza los enlaces de "wos". + - Obtiene el nombre del fichero a partir de la URL de descarga. + - Establece la subcarpeta dentro de la raíz basada en el tipo de archivo. + - Verifica si el fichero está en formato .zip. + - Calcula el nombre del fichero si es un zip. + + Modifica: + - ELEMENTS (list): Actualiza cada diccionario en ELEMENTS con las claves 'root_folder', 'url', 'file_name', 'subfolder', 'is_zip', y 'non_zip_file_name'. + + Ejemplo: + >>> process_elements() + """ + global ELEMENTS + for i in range(len(ELEMENTS)): # Construye el nombre de la carpeta raiz - if should_sort_by_developer: - elements[i]["root_folder"] = f"{elements[i]['title']} ({elements[i]['release_year']})" + if SHOULD_SORT_BY_DEVELOPER: + ELEMENTS[i]["root_folder"] = f"{ELEMENTS[i]['title']} ({ELEMENTS[i]['release_year']})" else: - elements[i]["root_folder"] = f"{elements[i]['title']} ({elements[i]['release_year']})({elements[i]['developer']})" - elements[i]["root_folder"] = normalize_path(elements[i]["root_folder"]) + ELEMENTS[i]["root_folder"] = f"{ELEMENTS[i]['title']} ({ELEMENTS[i]['release_year']})({ELEMENTS[i]['developer']})" + ELEMENTS[i]["root_folder"] = normalize_path(ELEMENTS[i]["root_folder"]) # Añade el prefijo a la url y normaliza los enlaces de "wos" - update_url(elements[i], url_prefix) + update_url(ELEMENTS[i], URL_PREFIX) # Obtiene el nombre del fichero a partir de la url de descarga - elements[i]["file_name"] = url_filename(elements[i]["url"]) + ELEMENTS[i]["file_name"] = url_filename(ELEMENTS[i]["url"]) # Establece la subcarpeta dentro de la raiz - elements[i]["subfolder"] = normalize_path(elements[i]["filetype"]) if elements[i]["filetype"] not in filetypes_on_root else "" + ELEMENTS[i]["subfolder"] = normalize_path(ELEMENTS[i]["filetype"]) if ELEMENTS[i]["filetype"] not in FILETYPES_ON_ROOT else "" # Averigua si el fichero está en formato .zip - elements[i]["is_zip"] = elements[i]["file_name"].lower().endswith(".zip") + ELEMENTS[i]["is_zip"] = ELEMENTS[i]["file_name"].lower().endswith(".zip") # Calcula el nombre del fichero si es un zip - elements[i]["non_zip_file_name"] = elements[i]["file_name"][:-4] if elements[i]["is_zip"] else elements[i]["file_name"] + ELEMENTS[i]["non_zip_file_name"] = ELEMENTS[i]["file_name"][:-4] if ELEMENTS[i]["is_zip"] else ELEMENTS[i]["file_name"] + -# Devuelve el fichero que forma la parte final de una URL def url_filename(url): + """ + Devuelve el fichero que forma la parte final de una URL. + + Parámetros: + url (str): La URL de la cual se quiere extraer el nombre del fichero. + + Retorna: + str: El nombre del fichero que forma la parte final de la URL. + + Ejemplo: + >>> url = "https://example.com/path/to/file.txt" + >>> url_filename(url) + 'file.txt' + """ parsed_url = urlparse(url) path = parsed_url.path filename = os.path.basename(path) return filename -# Descarga un fichero a partir de una URL + def download_file(url, destination): + """ + Descarga un fichero a partir de una URL. + + Parámetros: + url (str): La URL del fichero que se desea descargar. + destination (str): La ruta de destino donde se guardará el fichero descargado. + + Retorna: + bool: True si la descarga fue exitosa, False en caso de error. + + Comportamiento: + - Crea una sesión de requests con un adaptador que permite reintentos en caso de fallos. + - Intenta descargar el fichero desde la URL especificada. + - Guarda el contenido del fichero en la ruta de destino especificada. + - Maneja errores de conexión y otros problemas de la red, registrando cualquier error ocurrido. + + Ejemplo: + >>> success = download_file("https://example.com/file.zip", "/path/to/destination/file.zip") + >>> if success: + >>> print("Descarga completada con éxito.") + >>> else: + >>> print("Error en la descarga.") + """ session = requests.Session() retries = Retry(total=3, backoff_factor=1, status_forcelist=[500, 502, 503, 504]) session.mount('https://', HTTPAdapter(max_retries=retries)) @@ -171,8 +279,31 @@ def download_file(url, destination): logging.error(f"Error al descargar el archivo: {e}") return False -# Descomprime los ficheros que coinciden con la lista de extensiones + def unzip_file(src, dst): + """ + Descomprime los ficheros que coinciden con la lista de extensiones. + + Parámetros: + src (str): Ruta del archivo ZIP que se desea descomprimir. + dst (str): Ruta del directorio donde se extraerán los ficheros. + + Extensiones soportadas: + .z80, .sna, .tzx, .tap, .dsk, .trd + + Comportamiento: + - Abre el archivo ZIP especificado por `src`. + - Extrae los ficheros que coinciden con las extensiones definidas en el directorio especificado por `dst`. + - Maneja errores de archivo ZIP corrupto, archivo no encontrado y otros errores generales, registrándolos adecuadamente. + + Excepciones: + zipfile.BadZipFile: El archivo ZIP está corrupto. + FileNotFoundError: El archivo ZIP no se encontró. + Exception: Cualquier otro error inesperado. + + Ejemplo: + >>> unzip_file('/path/to/archivo.zip', '/path/to/destination/') + """ archive = src directory = dst extensions = (".z80", ".sna", ".tzx", ".tap", ".dsk", ".trd") @@ -190,8 +321,26 @@ def unzip_file(src, dst): except Exception as e: logging.error(f"Ocurrió un error: {e}") -# Imprime el estado de un archivo en el proceso de descarga + def print_status(current_file, total_files, element, total_files_width, status="cached"): + """ + Imprime el estado de un archivo en el proceso de descarga. + + Parámetros: + current_file (int): El número del archivo actual en el proceso de descarga. + total_files (int): El número total de archivos en el proceso de descarga. + element (dict): Diccionario que contiene información del archivo actual, incluyendo 'file_name' y 'filetype'. + total_files_width (int): El ancho del campo para el número total de archivos, para un formato de impresión alineado. + status (str): El estado del archivo (por defecto es "cached"). + + Comportamiento: + - Imprime el estado del archivo actual en el proceso de descarga de una manera formateada y alineada. + + Ejemplo: + >>> element = {"file_name": "example.zip", "filetype": "zip"} + >>> print_status(1, 100, element, 3) + ( 1 / 100) : cached : example.zip (zip) + """ print( "({:{width}} / {}) : {:<10} : {} ({})".format( current_file, @@ -203,34 +352,56 @@ def print_status(current_file, total_files, element, total_files_width, status=" ) ) -# Compone la carpeta de destino en función de varios parámetros + def get_final_destination_folder(developer, year, root_folder): + """ + Compone la carpeta de destino en función de varios parámetros. + + Parámetros: + developer (str): Nombre del desarrollador del juego. + year (int o str): Año de lanzamiento del juego. + root_folder (str): Nombre de la carpeta raíz. + + Retorna: + str: La ruta completa de la carpeta de destino final. + + Comportamiento: + - Determina la carpeta base (`folder1`) en función de si el juego es clásico o moderno, el desarrollador o el año. + - Determina la subcarpeta (`folder2`) basada en el desarrollador, si está habilitado. + - Determina la subcarpeta (`folder3`) basada en el año de lanzamiento, si está habilitado. + - Determina la subcarpeta (`folder4`) basada en la primera letra del nombre de la carpeta raíz, si está habilitado. + - Combina las carpetas calculadas y la carpeta raíz para obtener la ruta final de la carpeta de destino. + + Ejemplo: + >>> get_final_destination_folder("Nintendo", 1985, "Super Mario") + 'by_developer/N/Nintendo/1985/Super Mario' + """ # Carpeta basada en los años de vida comercial del spectrum folder1 = "" - if should_split_modern_and_classic: - if year == "none" or year is None or year > last_classic_year: + if SHOULD_SPLIT_MODERN_AND_CLASSIC: + if year == "none" or year is None or year > LAST_CLASSIC_YEAR: folder1 = "modern" else: folder1 = "classics" - elif should_sort_by_developer: + elif SHOULD_SORT_BY_DEVELOPER: folder1 = "by_developer" - elif should_sort_by_year: + elif SHOULD_SORT_BY_YEAR: folder1 = "by_year" # Carpeta basada en el desarrollador folder2 = "" - if should_sort_by_developer: + if SHOULD_SORT_BY_DEVELOPER: developer = developer or "" folder2 = os.path.join(developer[0].upper(), developer) # Carpeta basada en el año de lanzamiento folder3 = "" - if should_sort_by_year: + if SHOULD_SORT_BY_YEAR: folder3 = str(year) if year not in [None, "none"] else "unknown" # Carpeta basada en la primera letra del nombre de la carpeta raíz folder4 = "" - if should_sort_by_letter: + if SHOULD_SORT_BY_LETTER: if root_folder[0].isdigit(): folder4 = "0-9" else: @@ -243,29 +414,63 @@ def get_final_destination_folder(developer, year, root_folder): return os.path.join(folder1, folder2, folder3, folder4, root_folder) -# Crea las carpetas de destino y copia o extrae el archivo de la caché + def process_cache_file(cache_file, destination_subfolder, destination_file, element): + """ + Crea las carpetas de destino y copia o extrae el archivo de la caché. + + Parámetros: + cache_file (str): Ruta del archivo en la caché. + destination_subfolder (str): Ruta del subdirectorio de destino donde se guardarán los archivos extraídos. + destination_file (str): Ruta del archivo de destino si no se extrae. + element (dict): Diccionario que contiene información del archivo, incluyendo si tiene subcarpeta específica. + + Comportamiento: + - Crea las carpetas de destino necesarias. + - Si el archivo en caché es un zip y no tiene subcarpeta especificada, descomprime el archivo en el subdirectorio de destino. + - Si no, copia el archivo en caché al archivo de destino especificado. + + Ejemplo: + >>> process_cache_file('/path/to/cache.zip', '/path/to/destination/subfolder', '/path/to/destination/file', element) + """ os.makedirs(destination_subfolder, exist_ok=True) if cache_file.endswith(".zip") and element["subfolder"] == "": unzip_file(cache_file, destination_subfolder) else: shutil.copyfile(cache_file, destination_file) -# Obtiene los ficheros de la consulta desde internet o desde la caché -# y los deposita en la carpeta destino, descomprimiendo los archivos necesarios + def get_files(): + """ + Obtiene los ficheros de la consulta desde internet o desde la caché y los deposita en la carpeta destino, + descomprimiendo los archivos necesarios. + + Comportamiento: + - Presenta en pantalla el progreso de la descarga. + - Para cada elemento en ELEMENTS: + - Calcula la carpeta de clasificación basada en el desarrollador, año de lanzamiento y carpeta raíz. + - Determina las rutas de la carpeta de destino y de caché. + - Si el fichero no existe en la carpeta de destino: + - Si el fichero existe en la caché, lo copia al destino. + - Si no existe en la caché, lo descarga y lo guarda en la caché y en el destino. + - Si el fichero ya existe en el destino, lo omite. + - Maneja errores durante el procesamiento, registrándolos adecuadamente. + + Ejemplo: + >>> get_files() + """ # Variables para la presentación en pantalla de la descarga current_file = 0 - total_files = len(elements) + total_files = len(ELEMENTS) total_files_width = len(str(total_files)) last_game_folder = "" - for element in elements: + for element in ELEMENTS: classification_folder = get_final_destination_folder(element["developer"], element["release_year"], element["root_folder"]) - destination_folder = os.path.join(destination_path, classification_folder) + destination_folder = os.path.join(DESTINATION_PATH, classification_folder) destination_subfolder = os.path.join(destination_folder, element["subfolder"]) - cache_folder = os.path.join(cache_path, element["root_folder"]) + cache_folder = os.path.join(CACHE_PATH, element["root_folder"]) cache_subfolder = os.path.join(cache_folder, element["subfolder"]) # Ruta completa hasta el fichero de destino y de caché @@ -290,20 +495,20 @@ def get_files(): # Si no existe en la caché, lo descarga else: - if download_file(element["url"], temp_file): + if download_file(element["url"], TEMP_FILE): print_status(current_file, total_files, element, total_files_width, status="downloaded") - if os.path.isfile(temp_file): + if os.path.isfile(TEMP_FILE): # Mueve el fichero temporal descargado a la cache os.makedirs(cache_subfolder, exist_ok=True) - shutil.move(temp_file, cache_file) + shutil.move(TEMP_FILE, cache_file) # Copia el fichero de la cache al destino if os.path.isfile(cache_file): process_cache_file(cache_file, destination_subfolder, destination_file, element) else: print_status(current_file, total_files, element, total_files_width, status="not found") - if wait: - time.sleep(random.randint(min_wait, max_wait)) + if WAIT: + time.sleep(random.randint(MIN_WAIT, MAX_WAIT)) # Si el fichero ya existe en el destino, no hace nada else: @@ -312,16 +517,49 @@ def get_files(): except Exception as e: logging.error(f"Error al procesar el fichero {element['file_name']}: {e}") -# Elimina los caracteres ilegales de la cadena de texto + def normalize_path(path): + """ + Elimina los caracteres ilegales de la cadena de texto. + + Parámetros: + path (str): La cadena de texto que se desea normalizar. + + Retorna: + str: La cadena de texto normalizada sin los caracteres ilegales. + + Comportamiento: + - Reemplaza los caracteres ilegales en la cadena de texto con un carácter vacío. + - Los caracteres ilegales incluyen: <, >, :, ", /, \, |, ?, *. + - Utiliza la función unidecode para manejar caracteres Unicode. + + Ejemplo: + >>> normalize_path("file:name/with|illegal*chars?") + 'filenamewithillegalchars' + """ illegal_chars = ["<", ">", ":", '"', "/", "\\", "|", "?", "*"] replace_with = "" for char in illegal_chars: path = unidecode(path.replace(char, replace_with)) return path -# Elimina los subdirectorios vacios + def remove_empty_directories(path): + """ + Elimina los subdirectorios vacíos. + + Parámetros: + path (str): La ruta del directorio raíz desde donde se iniciará la eliminación de subdirectorios vacíos. + + Comportamiento: + - Recorre la estructura de directorios desde el `path` especificado, de forma descendente. + - Intenta eliminar cada subdirectorio encontrado. + - Registra un mensaje informativo si un subdirectorio vacío es eliminado. + - Si no se puede eliminar un directorio porque no está vacío u ocurre otro error, el error es ignorado. + + Ejemplo: + >>> remove_empty_directories('/path/to/directory') + """ for root, dirs, files in os.walk(path, topdown=False): for dir in dirs: dir_path = os.path.join(root, dir) @@ -332,12 +570,24 @@ def remove_empty_directories(path): # El directorio no está vacío o ocurrió otro error pass -# Limpia la carpeta de destino + def clear_destination_folder(): - if should_clear_destination_path: + """ + Limpia la carpeta de destino. + + Comportamiento: + - Verifica si la opción de limpiar la carpeta de destino (`SHOULD_CLEAR_DESTINATION_PATH`) está habilitada. + - Si está habilitada, elimina todos los archivos y subdirectorios en la carpeta de destino (`DESTINATION_PATH`). + - Registra un mensaje informativo para cada archivo o directorio eliminado. + - Maneja y registra cualquier error que ocurra durante la eliminación. + + Ejemplo: + >>> clear_destination_folder() + """ + if SHOULD_CLEAR_DESTINATION_PATH: logging.info("Limpiando la carpeta de destino ...") - for filename in os.listdir(destination_path): - file_path = os.path.join(destination_path, filename) + for filename in os.listdir(DESTINATION_PATH): + file_path = os.path.join(DESTINATION_PATH, filename) try: if os.path.isfile(file_path) or os.path.islink(file_path): os.unlink(file_path) @@ -348,18 +598,34 @@ def clear_destination_folder(): except Exception as e: logging.error(f'No se pudo eliminar {file_path}. Razón: {e}') -# Imprime la lista de elementos + def print_elements(mode=0): + """ + Imprime la lista de elementos en diferentes modos. + + Parámetros: + mode (int): El modo de impresión. + - Si es 0, imprime todos los elementos con sus claves y valores. + - Si es 1, imprime los nombres de las carpetas raíz eliminando duplicados y muestra el número de entradas únicas. + + Comportamiento: + - Modo 0: Recorre todos los elementos en ELEMENTS e imprime cada clave y valor en un formato legible. + - Modo 1: Elimina duplicados basándose en la carpeta raíz ('root_folder') y los imprime. Luego, muestra el número total de entradas únicas. + + Ejemplo: + >>> print_elements(0) + >>> print_elements(1) + """ if mode == 0: # Primer bucle for - for element in elements: + for element in ELEMENTS: print('') for key, value in element.items(): print(key, ':', value) elif mode == 1: # Segundo bucle for con eliminación de duplicados seen = set() - for element in elements: + for element in ELEMENTS: root_folder = element['root_folder'] if root_folder not in seen: print(root_folder) @@ -367,14 +633,30 @@ def print_elements(mode=0): # Imprimir el número de elementos únicos print(f"Número de entradas: {len(seen)}") -# Bucle principal + def main(): + """ + Bucle principal que ejecuta las principales funciones del programa. + + Comportamiento: + - Establece la conexión a la base de datos y ejecuta la consulta con `query_index=3`. + - Procesa todos los elementos, modificando sus parámetros. + - Imprime la lista de elementos en el modo 1, que elimina duplicados y muestra el número total de entradas únicas. + - Limpia la carpeta de destino si la opción está habilitada. + - Obtiene los archivos desde internet o desde la caché, y los deposita en la carpeta de destino, descomprimiendo los necesarios. + - Elimina los subdirectorios vacíos en la carpeta de destino. + + Ejemplo: + >>> main() + """ + connect(query_index=3) process_elements() print_elements(mode=1) clear_destination_folder() get_files() - remove_empty_directories(destination_path) + remove_empty_directories(DESTINATION_PATH) if __name__ == "__main__": main() +