#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
 API de ogRepository, programada en Flask.

Responde a peticiones HTTP (en principio, enviadas desde ogCore) mediante endpoints, que a su vez ejecutan los scripts Python almacenados en ogRepository.
En ciertos casos, transforma los parámetros recibidos desde el portal, para adaptarlos a los que es necesario enviar a los scripts
  (por ejemplo, a partir del ID de una imagen obtiene su nombre y su extensión).

Librerías Python requeridas: - flask (se puede instalar con "sudo apt install python3-flask")
                             - paramiko (se puede instalar con "sudo apt install python3-paramiko")
                             - requests (se puede instalar con "sudo apt install python3-requests") - No es necesario instalarlo en Ubuntu 24
                             - flasgger (se puede instalar con "sudo apt install python3-flasgger")
"""

# --------------------------------------------------------------------------------------------
#                                        IMPORTS
# --------------------------------------------------------------------------------------------

from flask import Flask, jsonify, request, Blueprint
import os
import subprocess
import json
from time import sleep
import paramiko
import logging
import threading
import requests
import random
import hashlib
import psutil
from systemd import journal
# Imports para Swagger:
from flasgger import Swagger
import yaml


# --------------------------------------------------------------------------------------------
#                                        VARIABLES
# --------------------------------------------------------------------------------------------

repo_path = '/opt/opengnsys/ogrepository/images/' # No borrar la barra final
vm_path = '/opt/opengnsys/ogrepository/images_virtual/' # No borrar la barra final
script_path = '/opt/opengnsys/ogrepository/bin'
repo_file = '/opt/opengnsys/ogrepository/etc/repoinfo.json'
trash_file = '/opt/opengnsys/ogrepository/etc/trashinfo.json'
config_file = '/opt/opengnsys/ogrepository/etc/ogAdmRepo.cfg'


# --------------------------------------------------------------------------------------------
#                                        FUNCTIONS
# --------------------------------------------------------------------------------------------

blueprint = Blueprint('repo_api', __name__, template_folder='templates')
app = blueprint

def get_IPcore():
    """ Obtiene el valor asociado a la variable "IPcore", desde el archivo '/opt/opengnsys/ogrepository/etc/ogAdmRepo.cfg'.
        Retorna la IP encontrada (que corresponde a la IP de ogCore), o un error (si no la encuentra).
    """
    journal.send("Running function 'get_IPcore'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    IPcore = None
    with open(config_file, 'r') as file:
        for line in file:
            if line.startswith('IPcore'):
                IPcore = line.split('=')[1].strip()
                journal.send(f"ogCore IP obtained ({IPcore})", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
                journal.send(f"{{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function get_IPcore', 'desc':'ogCore IP obtained ({IPcore})'}}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
                return IPcore
    if IPcore is None:
        journal.send("Can't obtain ogCore IP", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send("{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function get_IPcore', 'desc':'Unable to obtain ogCore IP'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
        return "IP no encontrada en el archivo de configuración"


# ---------------------------------------------------------


def get_image_params(image_id, search='all'):
    """ A partir de un ID de imagen (que corresponde al "fullsum"), busca la imagen en el repositorio y/o en la papelera (dependiendo del parámetro "search").
        Si encuentra la imagen devuelve su nombre y su extensión en un diccionario, y si no encuentra la imagen especificada retorna "None".
        El parámtro "search" tiene el valor predeterminado "all" (que hará que busque tanto en el repo como en la papelera),
          pero se le puede pasar el valor "repo" (para que busque solo en el repo) o "trash" (para que busque solo en la papelera).
    """
    journal.send("Running function 'get_image_params'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    # Creamos un diccionario vacío, para almacenar los resultados:
    result = {}

    # Abrimos y almacenamos el archivo "repoinfo.json" (solo si se ha de buscar en el repo, y si el archivo tiene contenido):
    if (search == 'all' or search == 'repo') and os.path.getsize(repo_file) > 0:
        with open(repo_file, 'r') as file:
            repo_data = json.load(file)
        # Iteramos la clave "images" y buscamos la imagen (y si la encontramos almacenamos el nombre y la extension):
        for image in repo_data.get('images', []):
            if image.get('fullsum') == image_id:
                result['name'] = image.get('name')
                result['extension'] = image.get('type')
                journal.send("Image found in repository JSON file", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
                journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function get_image_params', 'desc':'Image found in repository JSON file'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
                return result

    # Abrimos y almacenamos el archivo "trashinfo.json" (solo si se ha de buscar en la papelera, y si el archivo tiene contenido):
    if (search == 'all' or search == 'trash') and os.path.getsize(trash_file) > 0:
        with open(trash_file, 'r') as file:
            trash_data = json.load(file)
        # Iteramos la clave "images" y buscamos la imagen (y si la encontramos almacenamos el nombre y la extension):
        for image in trash_data.get('images', []):
            if image.get('fullsum') == image_id:
                result['name'] = image.get('name')
                result['extension'] = image.get('type')
                journal.send("Image found in trash JSON file", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
                journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function get_image_params', 'desc':'Image found in trash JSON file'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
                return result

    # Si no encontramos la imagen, retornamos "None":
    journal.send("Image not found in JSON file", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
    journal.send("{'component':'ogRepo', 'severity':'WARNING', 'operation':'Run function get_image_params', 'desc':'Image not found in JSON file'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
    return None


# ---------------------------------------------------------


def search_process(process, string_to_search):
    """ Busca procesos que contengan el valor del parámetro "process" y el valor del parámetro "string_to_search" (la ruta de la imagen, normalmente).
        Si encuentra alguno retorna "True", y si no encuentra ninguno retorna "False".
    """
    journal.send("Running function 'search_process'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    try:
        # Obtenemos todos los procesos que están corriendo, y almacenamos la salida y los errores:
        result = subprocess.Popen(['ps', '-aux'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8')
        out, error = result.communicate()

        # Almacenamos en una lista los procesos que contengan el proceso del parámetro y la cadena a buscar:
        process_list = [line for line in out.split('\n') if process in line and string_to_search in line]

        # Si hemos encontrado algún proceso que cumpla las condiciones, retornamos "True", y si no retornamos "False":
        if process_list != []:
            journal.send("Process found", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function search_process', 'desc':'Process found'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
            return True
        else:
            journal.send("Process not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'WARNING', 'operation':'Run function search_process', 'desc':'Process not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
            return False
    # Si se ha producido una excepción, imprimimos el error:
    except Exception as error_description:
        journal.send(f"Function 'search_process' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function search_process', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
        print(f"Unexpected error: {error_description}")


# ---------------------------------------------------------


def check_remote_connection(remote_ip, remote_user):
    """ Comprueba la conexión SSH/SFTP con el servidor remoto que recibe como primer parámetro.
        Se utiliza para chequear la conexión antes de importar o exportar una imagen.
    """
    journal.send("Running function 'check_remote_connection'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    try:
        # Iniciamos un cliente SSH:
        ssh_client = paramiko.SSHClient()
        # Establecemos la política por defecto para localizar la llave del host localmente:
        ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

        # Conectamos con el equipo remoto por SSH (con claves):
        ssh_client.connect(hostname=remote_ip, port=22, username=remote_user, passphrase='')

        # Iniciamos un cliente SFTP:
        sftp_client = ssh_client.open_sftp()

        # Cerramos el cliente SSH y el cliente SFTP:
        ssh_client.close()
        sftp_client.close()

        # Retornamos "True", porque hemos conseguido conectar:
        journal.send("Connection OK", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function check_remote_connection', 'desc':'Connection OK'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
        return True

    # Si se produce una excepción, retornamos "False":
    except Exception as error_description:
        journal.send(f"Function 'check_remote_connection' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function check_remote_connection', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
        return False


# ---------------------------------------------------------


def check_remote_image(remote_ip, remote_user, image_file_path):
    """ Conecta con el servidor remoto que recibe como primer parámetro,
          para comprobar si la imagen que recibe como tercer parámetro existe, o si está bloqueada.
        Se utiliza para chequear la imagen antes de importarla.
    """
    journal.send("Running function 'check_remote_image'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    # Iniciamos un cliente SSH:
    ssh_client = paramiko.SSHClient()
    # Establecemos la política por defecto para localizar la llave del host localmente:
    ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

    # Conectamos con el equipo remoto por SSH (con claves):
    ssh_client.connect(hostname=remote_ip, port=22, username=remote_user, passphrase='')

    # Iniciamos un cliente SFTP:
    sftp_client = ssh_client.open_sftp()

    # Si la imagen no existe, retornamos el mensaje correspondiente:
    try:
        sftp_client.stat(image_file_path)
    except IOError:
        return "Remote image not found"

    # Si la imagen existe pero está bloqueada, retornamos el mensaje correspondiente:
    try:
        sftp_client.stat(f"{image_file_path}.lock")
        return "Remote image is locked"
    except IOError:
        print("Remote image is not locked, as expected")

    # Cerramos el cliente SSH y el cliente SFTP:
    ssh_client.close()
    sftp_client.close()


# ---------------------------------------------------------


def check_lock_local(image_file_path, job_id):
    """ Cada minuto comprueba si existe un archivo ".lock" asociado a la imagen que recibe como parámetro, y si existe la imagen y sus archivos asociados.
        Cuando ya no exista el archivo ".lock" (pero si los demás archivos), le comunicará a ogCore que la importación ha sido exitosa, y saldrá de bucle.
        Cuando ya no exista el archivo ".lock" (pero tampoco los demás), le comunicará a ogCore que la importación ha fallado, y saldrá de bucle.
        Mientras no se cumpla ninguna de las condiciones anteriores, y aun exista el archivo ".lock", seguirá haciendo la comprobación (repitiendo el bucle cada minuto).
    """
    journal.send("Running function 'check_lock_local'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    # Esperamos 30 segundos, para dar tiempo a que se cree el archivo ".lock":
    sleep(30)

    # Creamos un bucle infinito:
    while True:
        # Si ya no existe el archivo ".lock" (pero sí existen los demás), respondemos a ogCore (con "success: True") y salimos del bucle:
        if not os.path.exists(f"{image_file_path}.lock") and os.path.exists(image_file_path) and os.path.exists(f"{image_file_path}.full.sum") and os.path.exists(f"{image_file_path}.info.checked") and os.path.exists(f"{image_file_path}.size") and os.path.exists(f"{image_file_path}.sum") and os.path.exists(f"{image_file_path}.torrent"):
            journal.send("Task finalized (image unlocked)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function check_lock_local', 'desc':'Image unlocked'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")

            # Almacenamos en un diccionario los datos a enviar a ogCore:
            data = {
                'job_id': job_id,
                'success': True
            }
            # Llamamos al endpoint de ogCore, enviando los datos del diccionario:
            journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: True)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            recall_ogcore(data)
            break
        # Si no existe el archivo ".lock" (pero tampoco los demás), respondemos a ogCore (con "success: False") y salimos del bucle:
        elif not os.path.exists(f"{image_file_path}.lock") and not os.path.exists(image_file_path) and not os.path.exists(f"{image_file_path}.full.sum") and not os.path.exists(f"{image_file_path}.info") and not os.path.exists(f"{image_file_path}.size") and not os.path.exists(f"{image_file_path}.sum") and not os.path.exists(f"{image_file_path}.torrent"):
            journal.send("Imported image didn't pass the Integrity Check", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function check_lock_local', 'desc':'Integrity check failed'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")

            # Almacenamos en un diccionario los datos a enviar a ogCore:
            data = {
                'job_id': job_id,
                'success': False
            }
            # Llamamos al endpoint de ogCore, enviando los datos del diccionario:
            journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: False)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            recall_ogcore(data)
            break
        # Si aun existe el archivo ".lock" (y no se han cumplido las condiciones anteriores), imprimimos un mensaje en la API:
        elif os.path.exists(f"{image_file_path}.lock"):
            journal.send("Task in process (.lock file exists)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

        # Esperamos 1 minuto para volver a realizar la comprobación:
        sleep(60)


# ---------------------------------------------------------


def check_remote_backup(image_name, remote_ip, remote_user, remote_path, job_id):
    """ Cada minuto comprueba si se ha copiado la imagen que recibe como primer parámetro (y sus archivos asociados) al equipo remoto que recibe como segundo parámetro,
          y mientras eso no suceda seguirá haciendo la comprobación (en un bucle "while" que se ejecuta cada minuto).
        Una vez copiados todos los archivos, chequea la integridad de la imagen, comparando el contenido de los archivos ".sum" y ".size" con los valores reales (que vuelve a calcular).
        Si la comprobación es correcta saldrá del bucle, y si es incorrecta saldrá del bucle y borrará los archivos que se hayan copiado al equipo remoto
          (y en ambos casos le comunicará a ogCore el resultado, llamando a la función "recall_ogcore").
    """
    journal.send("Running function 'check_remote_backup'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    # Iniciamos un cliente SSH:
    ssh_client = paramiko.SSHClient()
    # Establecemos la política por defecto para localizar la llave del host localmente:
    ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    # Conectamos con el equipo remoto por SSH (con claves):
    ssh_client.connect(hostname=remote_ip, port=22, username=remote_user, passphrase='')
    # Iniciamos un cliente SFTP:
    sftp_client = ssh_client.open_sftp()

    # Esperamos 30 segundos antes de empezar a realizar la comprobación:
    sleep(30)

    # Creamos una lista con las extensiones de los archivos asociados a la imagen (incluyendo ninguna extensión, que corresponde a la propia imagen):
    extensions = ['', '.size', '.sum', '.full.sum', '.info']

    # Creamos un bucle infinito:
    while True:
        # Comprobamos si ya se ha copiado la imagen y cada uno de sus archivos asociados (y almacenamos el resultado):
        try:
            for ext in extensions:
                sftp_client.stat(f"{remote_path}{image_name}{ext}")
                all_files_copied = True
        except IOError:
            all_files_copied = False

        # Si se han copiado todos los archivos, comprobamos la integridad de la imagen:
        if all_files_copied == True:
            journal.send("All files copied", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

            try:
                # Calculamos el tamaño del la imagen exportada ("size")
                file_size = sftp_client.stat(f"{remote_path}{image_name}").st_size

                # Calculamos el hash MD5 del último MB de la imagen exportada ("sum"):
                last_mb_offset = max(0, file_size - 1024 * 1024)
                with sftp_client.file(f"{remote_path}{image_name}", 'rb') as file:
                    file.seek(last_mb_offset)
                    last_mb = file.read(1024 * 1024)
                file_sum = hashlib.md5(last_mb).hexdigest()

                # Obtenemos el valor almacenado en el archivo ".size:
                with sftp_client.file(f"{remote_path}{image_name}.size", 'r') as file:
                    stored_size = int(file.read().strip())

                # Obtenemos el valor almacenado en el archivo ".sum:
                with sftp_client.file(f"{remote_path}{image_name}.sum", 'r') as file:
                    stored_sum = file.read().strip().decode('utf-8')

            except Exception as error:
                journal.send(f"Integrity check returned an error: {error}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
                journal.send("{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function check_remote_backup', 'desc':'Integrity check returned an error'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")

                # Borramos los archivos copiados, porque no hemos podido chequear la integridad de la imagen:
                remove_remote_files(sftp_client, remote_path, image_name, extensions)

                # Almacenamos en un diccionario los datos a enviar a ogCore:
                data = {
                    'job_id': job_id,
                    'success': False
                }
                # Llamamos al endpoint de ogCore, enviando los datos del diccionario:
                journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: False)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
                recall_ogcore(data)
                break

            # Comprobamos si los datos almacenados coinciden con los datos obtenidos (en cuyo caso el backup habrá sido correcto):
            if file_sum == stored_sum and file_size == stored_size:
                journal.send("Task finalized (backup complete)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
                journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function check_remote_backup', 'desc':'Backup complete'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")

                # Almacenamos en un diccionario los datos a enviar a ogCore:
                data = {
                    'job_id': job_id,
                    'success': True
                }
                # Llamamos al endpoint de ogCore, enviando los datos del diccionario:
                journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: True)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
                recall_ogcore(data)
                break
            else:
                journal.send("Exported image didn't pass the Integrity Check", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
                journal.send("{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function check_remote_backup', 'desc':'Integrity check failed'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")

                # Borramos los archivos copiados, porque el chequeo de la integridad de la imagen no ha sido exitoso:
                remove_remote_files(sftp_client, remote_path, image_name, extensions)

                # Almacenamos en un diccionario los datos a enviar a ogCore:
                data = {
                    'job_id': job_id,
                    'success': False
                }
                # Llamamos al endpoint de ogCore, enviando los datos del diccionario:
                journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: False)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
                recall_ogcore(data)
                break
        else:
            journal.send("Task in process (backup incomplete)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

        # Esperamos 1 minuto para volver a realizar la comprobación:
        sleep(60)

    # Ya fuera del bucle, cerramos el cliente SSH y el cliente SFTP:
    ssh_client.close()
    sftp_client.close()


# ---------------------------------------------------------


def check_aux_files(image_file_path, job_id):
    """ Cada 10 segundos comprueba si se han creado todos los archivos auxiliares de la imagen que recibe como parámetro,
          en cuyo caso lo comunicará a ogCore, llamando a un endpoint, y dejará de realizar la comprobación.
        También obtiene el valor del archivo ".full.sum" (que corresonde al ID), y se lo comunica a ogCore.
    """
    journal.send("Running function 'check_aux_files'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    # Creamos un bucle infinito:
    while True:
        # Si faltan archivos auxiliares por crear, imprimimos un mensaje en la API:
        if not os.path.exists(f"{image_file_path}.size") or not os.path.exists(f"{image_file_path}.sum") or not os.path.exists(f"{image_file_path}.full.sum") or not os.path.exists(f"{image_file_path}.torrent") or not os.path.exists(f"{image_file_path}.info.checked"):
            journal.send("Task in process (auxiliar files remaining)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        # Si ya se han creado todos los archivos auxiliares, imprimimos un mensaje en la API, respondemos a ogCore y salimos del bucle:
        else:
            journal.send("Task finalized (all auxilar files created)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function check_aux_files', 'desc':'Auxiliar files created'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")

            # Obtenemos el valor del archivo "full.sum", que corresponde al ID:
            with open(f"{image_file_path}.full.sum", 'r') as file:
                image_id = file.read().strip('\n')

            # Almacenamos en un diccionario los datos a enviar a ogCore:
            data = {
                'job_id': job_id,
                'image_id': image_id
            }
            # Llamamos al endpoint de ogCore, enviando los datos del diccionario:
            journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, IMAGE_ID: {image_id})", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            recall_ogcore(data)
            break
        # Esperamos 10 segundos para volver a realizar la comprobación:
        sleep(10)


# ---------------------------------------------------------


def check_virtual_image_conversion(image_name, job_id):
    """ Cada minuto comprueba si existe la imagen convertida (y sus archivos asociados), o si no existe ninguno de los archivos que se crean en el proceso.
        Cuando ya no exista el archivo ".lock" (pero si los demás archivos), le comunicará a ogCore que la conversión ha sido exitosa, y saldrá de bucle.
        Cuando no exista ninguno de los archivos que se crean en el proceso (incluyendo la imagen convertida), le comunicará a ogCore que la conversión ha fallado, y saldrá de bucle.
        Mientras no se cumpla ninguna de las condiciones anteriores, seguirá haciendo la comprobación (repitiendo el bucle cada minuto).
        Lo utiliza el endpoint "Convertir imagen virtual a imagen OpenGnsys".
    """
    journal.send("Running function 'check_virtual_image_conversion'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    # Esperamos 30 segundos, para dar tiempo a que se cree algún archivo:
    sleep(30)

    # Construimos la ruta de la imagen (una vez convertida):
    image_file_path = f"{repo_path}{image_name}.img"

    # Creamos un bucle infinito:
    while True:
        # Si ya no existe el archivo ".lock" (pero sí existen los demás), respondemos a ogCore (con "success: True") y salimos del bucle:
        if not os.path.exists(f"{image_file_path}.lock") and os.path.exists(image_file_path) and os.path.exists(f"{image_file_path}.full.sum") and os.path.exists(f"{image_file_path}.info.checked") and os.path.exists(f"{image_file_path}.size") and os.path.exists(f"{image_file_path}.sum") and os.path.exists(f"{image_file_path}.torrent"):
            journal.send("Task finalized (Virtual image converted)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function check_virtual_image_conversion', 'desc':'Virtual image converted'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")

            # Almacenamos en un diccionario los datos a enviar a ogCore:
            data = {
                'job_id': job_id,
                'success': True
            }
            # Llamamos al endpoint de ogCore, enviando los datos del diccionario:
            journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: True)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            recall_ogcore(data)
            break

        # Si no existe ninguno de los archivos que se crean en el proceso (incluyendo la imagen convertida), respondemos a ogCore (con "success: False") y salimos del bucle:
        elif not os.path.exists(f"{vm_path}{image_name}.raw") and not os.path.exists(f"{vm_path}{image_name}.img") and not os.path.exists(f"{vm_path}{image_name}.img.lzo") and not os.path.exists(f"{repo_path}{image_name}.img"):
            journal.send("Virtual image conversion failed", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function check_virtual_image_conversion', 'desc':'Virtual image conversion failed'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")

            # Almacenamos en un diccionario los datos a enviar a ogCore:
            data = {
                'job_id': job_id,
                'success': False
            }
            # Llamamos al endpoint de ogCore, enviando los datos del diccionario:
            journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: False)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            recall_ogcore(data)
            break
        # Si no se han cumplido las condiciones anteriores), imprimimos un mensaje en la API:
        else:
            journal.send("Task in process (Conversion not finalized)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

        # Esperamos 1 minuto para volver a realizar la comprobación:
        sleep(60)


# ---------------------------------------------------------


def check_virtual_image_reconversion(image_name, vm_extension, job_id):
    """ Cada minuto comprueba si existe la imagen virtual exportada y si ya no existe ninguno de los archivos que se crean en el proceso (o si ya no existen estos archivos ni la imagen exportada).
        En el primer caso, le comunicará a ogCore que la conversión ha sido exitosa y saldrá de bucle, y en el segundo caso, le comunicará a ogCore que la conversión ha fallado, y saldrá de bucle.
        Mientras no se cumpla ninguna de las condiciones anteriores, seguirá haciendo la comprobación (repitiendo el bucle cada minuto).
        Lo utiliza el endpoint "Convertir imagen OpenGnsys a imagen virtual".
    """
    journal.send("Running function 'check_virtual_image_reconversion'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    # Esperamos 30 segundos, para dar tiempo a que se cree algún archivo:
    sleep(30)

    # Construimos la ruta de la imagen virtual (una vez exportada), y la ruta de exportación:
    virtual_image_file_path = f"{vm_path}export/{image_name}.{vm_extension}"
    virtual_export_path = f"{vm_path}export/"

    # Creamos un bucle infinito:
    while True:
        # Si ya no existen los archivos que se crean durante la conversión, pero si la imagen virtual exportada, respondemos a ogCore (con "success: True") y salimos del bucle:
        if not os.path.exists(f"{virtual_export_path}disk.raw") and not os.path.exists(f"{virtual_export_path}{image_name}.img") and os.path.exists(virtual_image_file_path):
            journal.send("Task finalized (Image converted to virtual)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function check_virtual_image_reconversion', 'desc':'Image converted to virtual'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")

            # Almacenamos en un diccionario los datos a enviar a ogCore:
            data = {
                'job_id': job_id,
                'success': True
            }
            # Llamamos al endpoint de ogCore, enviando los datos del diccionario:
            journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: True)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            recall_ogcore(data)
            break

        # Si no existen los archivos que se crean durante la conversión, pero tampoco la imagen virtual exportada, respondemos a ogCore (con "success: False") y salimos del bucle:
        elif not os.path.exists(f"{virtual_export_path}disk.raw") and not os.path.exists(f"{virtual_export_path}{image_name}.img") and not os.path.exists(virtual_image_file_path):
            journal.send("Image conversion to virtual failed", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function check_virtual_image_reconversion', 'desc':'Image conversion to virtual failed'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")

            # Almacenamos en un diccionario los datos a enviar a ogCore:
            data = {
                'job_id': job_id,
                'success': False
            }
            # Llamamos al endpoint de ogCore, enviando los datos del diccionario:
            journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: False)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            recall_ogcore(data)
            break
        # Si no se han cumplido las condiciones anteriores), imprimimos un mensaje en la API:
        else:
            journal.send("Task in process (Conversion not finalized)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

        # Esperamos 1 minuto para volver a realizar la comprobación:
        sleep(60)


# ---------------------------------------------------------


def recall_ogcore(data):
    """ Hace una petición HTTP de tipo POST a un endpoint de ogCore, enviando datos que dependen del caso.
        Se utiliza para informar a ogCore del resultado de una tarea asíncrona,
          que estaba corriendo en un proceso independiente (no controlado por los endpoints).
    """
    journal.send("Running function 'recall_ogcore'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    # Almacenamos la IP de ogCore:
    ogcore_ip = get_IPcore()

    # Almacenamos la URL del endpoint de ogCore:
    endpoint_url = f"https://{ogcore_ip}:8443/og-repository/webhook"

    # Almacenamos los headers, y convertiomos "data" a JSON:
    headers = {'content-type': 'application/json'}
    data = json.dumps(data)

    # Hacemos una petición POST al endpoint, enviando lo almacenado en "data":
    journal.send("Sending HTTP request...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
    response = requests.post(endpoint_url, data=data, headers=headers, verify=False)

    # Imprimimos el código de estado de la petición y la respuesta de ogCore:
    journal.send(f"HTTP Status Code: {response.status_code}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
    #journal.send(f"HTTP Response: {response.text}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
    journal.send(f"{{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function recall_ogcore', 'desc':'HTTP Status Code response: {response.status_code}'}}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")


# ---------------------------------------------------------


def check_file_exists(file_path):
    """ Comprueba la existencia del archivo cuya ruta recibe como parámetro.
        Si el archivo existe devuelve "True", y si no devuelve "False".
    """
    journal.send("Running function 'check_file_exists'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    # Comprobamos si existe el archivo de la ruta "file_path":
    if os.path.exists(file_path):
        return True
    else:
        return False


# ---------------------------------------------------------


def remove_remote_files(sftp_client, remote_path, image_name, extensions):
    """ Borra la imagen "image_name" y sus archivos asociados (cuyas extensiones están almacenadas en la lista "extensions")
          del equipo remoto al que nos hemos conectado previamente (mediante el objeto "sftp_client", que recibe como primer parámetro).
        Es llamada por la función "check_remote_backup", cuando la imagen exportada no pasa el test de integridad.
    """
    journal.send("Running function 'remove_remote_files'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    # Iteramos las extensiones de la lista "extensions", para eliminar la imagen "image_name" y sus archivos asociados:
    for ext in extensions:
        try:
            sftp_client.stat(f"{remote_path}{image_name}{ext}")
            sftp_client.remove(f"{remote_path}{image_name}{ext}")
            journal.send(f"File with extension {ext} removed", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        except IOError:
            journal.send(f"File with extension {ext} doesn't exist", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")


# ---------------------------------------------------------


def check_free_space(image_name_full, path):
    """ Comprueba si hay suficiente espacio en disco para convertir la imagen que recibe como parámetro
          (4 veces el tamaño del archivo), devolviendo "True" si lo hay, y "False" si no lo hay.
        Lo utilizan los endpoints "Convertir imagen virtual a imagen OpenGnsys" y "Convertir imagen OpenGnsys a imagen virtual".
    """
    # Obtenemos el tamaño de la imagen:
    img_size = int(os.path.getsize(f"{path}{image_name_full}"))
    # Obtenemos la cantidad de espacio libre en disco:
    disk = psutil.disk_usage('/')
    free_space = int(disk.free)

    # Si no hay suficiente espacio libre en disco (4 veces el tamaño de la imagen), devolvemos "False", y en caso contrario "True":
    if free_space < (img_size * 4):
        return False
    else:
        return True



# --------------------------------------------------------------------------------------------
#                                        ENDPOINTS
# --------------------------------------------------------------------------------------------


# 1 - Endpoint "Obtener Información de Estado de ogRepository" (SINCRONO):
@app.route("/ogrepository/v1/status", methods=['GET'])
def get_repo_status():
    """ Este endpoint devuelve información de CPU, memoria RAM, disco duro y el estado de ciertos servicios y procesos de ogRepository, en formato json.
        Para ello, ejecuta el script "getRepoStatus.py", que no recibe parámetros.
    """
    journal.send("Running endpoint 'Obtener Información de Estado de ogRepository'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    try:
        journal.send("Running script 'getRepoStatus.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        # Ejecutamos el script "getRepoStatus.py", y almacenamos el resultado:
        result = subprocess.run(['python3', f"{script_path}/getRepoStatus.py"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8')

        # Evaluamos el resultado de la ejecución, y devolvemos la respuesta:
        if result.returncode == 0:
            journal.send("Script 'getRepoStatus.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script getRepoStatus.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": True,
                "output": json.loads(result.stdout)
            }), 200
        else:
            journal.send(f"Script 'getRepoStatus.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script getRepoStatus.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "error": result.stderr
            }), 500
    except Exception as error_description:
        journal.send(f"Script 'getRepoStatus.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script getRepoStatus.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "exception": str(error_description)
        }), 500


# ---------------------------------------------------------


# 2 - Endpoint "Obtener Información de todas las Imágenes" (SINCRONO):
@app.route("/ogrepository/v1/images", methods=['GET'])
def get_repo_info():
    """ Este endpoint devuelve información de todas las imágenes contenidas en el repositorio (incluída la papelera), en formato json.
        Para ello, ejecuta el script "getRepoInfo.py", con el parámetro "all".
    """
    journal.send("Running endpoint 'Obtener Información de todas las Imágenes'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    try:
        journal.send("Running script 'getRepoInfo.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        # Ejecutamos el script "getRepoInfo.py" (con el parámetro "all"), y almacenamos el resultado:
        result = subprocess.run(['python3', f"{script_path}/getRepoInfo.py", 'all'], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8')

        # Evaluamos el resultado de la ejecución, y devolvemos la respuesta:
        if result.returncode == 0:
            journal.send("Script 'getRepoInfo.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script getRepoInfo.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": True,
                "output": json.loads(result.stdout)
            }), 200
        else:
            journal.send(f"Script 'getRepoInfo.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script getRepoInfo.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "error": result.stderr
            }), 500
    except Exception as error_description:
        journal.send(f"Script 'getRepoInfo.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script getRepoInfo.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "exception": str(error_description)
        }), 500


# ---------------------------------------------------------


# 3 - Endpoint "Obtener Información de una Imagen concreta" (SINCRONO):
@app.route("/ogrepository/v1/images/<string:imageId>", methods=['GET'])
def get_repo_image_info(imageId):
    """ Este endpoint devuelve información de la imagen especificada como parámetro, en formato json.
        Para ello, ejecuta el script "getRepoInfo.py", con el nombre de la imagen como parámetro.
    """
    journal.send("Running endpoint 'Obtener Información de una Imagen concreta'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    # Obtenemos el nombre y la extensión de la imagen:
    param_dict = get_image_params(imageId)

    # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devolver un error si no se ha encontrado la imagen:
    if param_dict:
        cmd = ['python3', f"{script_path}/getRepoInfo.py", f"{param_dict['name']}.{param_dict['extension']}"]
    else:
        journal.send("Image not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint get_repo_image_info', 'desc': 'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "error": "Image not found"
        }), 400

    try:
        journal.send("Running script 'getRepoInfo.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        # Ejecutamos el script "getRepoInfo.py" (con los parámetros almacenados), y almacenamos el resultado:
        result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8')

        # Evaluamos el resultado de la ejecución, y devolvemos la respuesta:
        if result.returncode == 0:
            journal.send("Script 'getRepoInfo.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script getRepoInfo.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": True,
                "output": json.loads(result.stdout)
            }), 200
        else:
            journal.send(f"Script 'getRepoInfo.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script getRepoInfo.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "error": result.stderr
            }), 500
    except Exception as error_description:
        journal.send(f"Script 'getRepoInfo.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script getRepoInfo.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "exception": str(error_description)
        }), 500


# ---------------------------------------------------------


# 4 - Endpoint "Actualizar Información del Repositorio" (SINCRONO):
@app.route("/ogrepository/v1/images", methods=['PUT'])
def update_repo_info():
    """ Este endpoint actualiza la información del repositorio y de la papelera, reflejándola en los archivos "repoinfo.json" y "trashinfo.josn".
        Para ello, ejecuta el script "updateRepoInfo.py", que a su vez ejecuta el script "updateTrashInfo.py".
    """
    journal.send("Running endpoint 'Actualizar Información del Repositorio'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    try:
        journal.send("Running script 'updateRepoInfo.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        # Ejecutamos el script "updateRepoInfo.py" (sin parámetros), y almacenamos el resultado:
        result = subprocess.run(['python3', f"{script_path}/updateRepoInfo.py"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8')

        # Evaluamos el resultado de la ejecución, y devolvemos una respuesta:
        if result.returncode == 0:
            journal.send("Script 'updateRepoInfo.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script updateRepoInfo.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": True,
                "output": "Repository info updated successfully"
            }), 200
        else:
            journal.send(f"Script 'updateRepoInfo.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script updateRepoInfo.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "error": result.stderr
            }), 500
    except Exception as error_description:
        journal.send(f"Script 'updateRepoInfo.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script updateRepoInfo.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")

        return jsonify({
            "success": False,
            "exception": str(error_description)
        }), 500


# ---------------------------------------------------------


# 5 - Endpoint "Chequear Integridad de Imagen" (SINCRONO):
@app.route("/ogrepository/v1/status/images/<string:imageId>", methods=['GET'])
def check_image(imageId):
    """ Este endpoint comprueba la integridad de la imagen especificada como parámetro,
          comparando el tamaño y el hash MD5 del último MB con los valores almacenados en los archivos "size" y "sum".
        Para ello, ejecuta el script "checkImage.py", con el nombre de la imagen como único parámetro.
    """
    journal.send("Running endpoint 'Chequear Integridad de Imagen'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    # Obtenemos el nombre y la extensión de la imagen:
    param_dict = get_image_params(imageId, "repo")

    # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devolver un error si no se ha encontrado la imagen:
    if param_dict:
        cmd = ['python3', f"{script_path}/checkImage.py", f"{param_dict['name']}.{param_dict['extension']}"]
    else:
        journal.send("Image not found (inexistent or deleted)", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint check_image', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "error": "Image not found (inexistent or deleted)"
        }), 400

    try:
        journal.send("Running script 'checkImage.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        # Ejecutamos el script "checkImage.py" (con los parámetros almacenados), y almacenamos el resultado:
        result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8')

        # Evaluamos el resultado de la ejecución, y devolvemos la respuesta:
        if result.returncode == 0:
            journal.send("Script 'checkImage.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script checkImage.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
            if "Error" in result.stdout:
                return jsonify({
                    "success": True,
                    "output": "Image file didn't pass the Integrity Check"
                }), 200
            else:
                return jsonify({
                    "success": True,
                    "output": "Image file passed the Integrity Check correctly"
                }), 200
        else:
            journal.send(f"Script 'checkImage.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script checkImage.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "error": result.stderr
            }), 500
    except Exception as error_description:
        journal.send(f"Script 'checkImage.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script checkImage.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "exception": str(error_description)
        }), 500


# ---------------------------------------------------------


# 6 - Endpoint "Eliminar una Imagen" (SINCRONO):
@app.route("/ogrepository/v1/images/<string:imageId>", methods=['DELETE'])
def delete_image(imageId):
    """ Este endpoint elimina la imagen especificada como parámetro (y todos sus archivos asociados),
          moviéndolos a la papelera o eliminándolos permanentemente (dependiendo del parámetro "method").
        Para ello, ejecuta el script "deleteImage.py", con el nombre de la imagen como primer parámetro, y el parámetro opcional "-p".
    """
    journal.send("Running endpoint 'Eliminar una Imagen'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    # Obtenemos el nombre y la extensión de la imagen:
    param_dict = get_image_params(imageId, "repo")
    # Almacenams el método de eliminación ("trash" o "permanent")
    method = request.values.get('method')

    # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devolver un error si no se ha encontrado la imagen:
    if param_dict:
        if method == "permanent":
            cmd = ['python3', f"{script_path}/deleteImage.py", f"{param_dict['name']}.{param_dict['extension']}", '-p']
        elif method == "trash":
            cmd = ['python3', f"{script_path}/deleteImage.py", f"{param_dict['name']}.{param_dict['extension']}"]
        else:
            journal.send("Incorrect method (must be 'permanent' or 'trash')", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint delete_image', 'desc':'Warning: Incorrect method (must be permanent or trash)'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "error": "Incorrect method (must be 'permanent' or 'trash')"
            }), 400
    else:
        journal.send("Image not found (inexistent or deleted)", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint delete_image', 'desc':'Warning: Image not found (inexistent or deleted)'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "error": "Image not found (inexistent or deleted)"
        }), 400

    try:
        journal.send("Running script 'deleteImage.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        # Ejecutamos el script "deleteImage.py" (con los parámetros almacenados), y almacenamos el resultado:
        result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8')

        # Evaluamos el resultado de la ejecución, y devolvemos una respuesta:
        if result.returncode == 0:
            journal.send("Script 'deleteImage.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script deleteImage.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": True,
                "output": "Image deleted successfully"
            }), 200
        else:
            journal.send(f"Script 'deleteImage.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script deleteImage.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "error": result.stderr
            }), 500
    except Exception as error_description:
        journal.send(f"Script 'deleteImage.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script deleteImage.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "exception": str(error_description)
        }), 500


# ---------------------------------------------------------


# 7 - Endpoint "Recuperar una Imagen" (SINCRONO):
@app.route("/ogrepository/v1/trash/images", methods=['POST'])
def recover_image():
    """ Este endpoint recupera la imagen especificada como parámetro (y todos sus archivos asociados),
          moviéndolos nuevamente al repositorio de imágenes, desde la papelera.
        Para ello, ejecuta el script "recoverImage.py", con el nombre de la imagen como único parámetro.
    """
    journal.send("Running endpoint 'Recuperar una Imagen'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    # Almacenamos el parámetro "ID_img" (enviado por JSON):
    json_data = json.loads(request.data)
    image_id = json_data.get("ID_img")

    # Obtenemos el nombre y la extensión de la imagen:
    param_dict = get_image_params(image_id, "trash")

    # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devolver un error si no se ha encontrado la imagen:
    if param_dict:
        cmd = ['python3', f"{script_path}/recoverImage.py", f"{param_dict['name']}.{param_dict['extension']}"]
    else:
        journal.send("Image not found (inexistent or recovered previously)", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint recover_image', 'desc':'Warning: Image not found (inexistent or recovered previously)'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "error": "Image not found (inexistent or recovered previously)"
        }), 400

    try:
        journal.send("Running script 'recoverImage.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        # Ejecutamos el script "recoverImage.py" (con los parámetros almacenados), y almacenamos el resultado:
        result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8')

        # Evaluamos el resultado de la ejecución, y devolvemos una respuesta:
        if result.returncode == 0:
            journal.send("Script 'recoverImage.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script recoverImage.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": True,
                "output": "Image recovered successfully"
            }), 200
        else:
            journal.send(f"Script 'recoverImage.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script recoverImage.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "error": result.stderr
            }), 500
    except Exception as error_description:
        journal.send(f"Script 'recoverImage.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script recoverImage.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "exception": str(error_description)
        }), 500


# ---------------------------------------------------------


# 8 - Endpoint "Eliminar una Imagen de la Papelera" (SINCRONO):
@app.route("/ogrepository/v1/trash/images/<string:imageId>", methods=['DELETE'])
def delete_trash_image(imageId):
    """ Este endpoint elimina permanentemente la imagen especificada como parámetro (y todos sus archivos asociados), desde la papelera.
        Para ello, ejecuta el script "deleteTrashImage.py", con el nombre de la imagen como único parámetro.
    """
    journal.send("Running endpoint 'Eliminar una Imagen de la Papelera'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    # Obtenemos el nombre y la extensión de la imagen:
    param_dict = get_image_params(imageId, "trash")

    # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devolver un error si no se ha encontrado la imagen:
    if param_dict:
        cmd = ['python3', f"{script_path}/deleteTrashImage.py", f"{param_dict['name']}.{param_dict['extension']}"]
    else:
        journal.send("Image not found at trash", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint delete_trash_image', 'desc':'Warning: Image not found at trash'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "error": "Image not found at trash"
        }), 400

    try:
        journal.send("Running script 'deleteTrashImage.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        # Ejecutamos el script "deleteTrashImage.py" (con los parámetros almacenados), y almacenamos el resultado:
        result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8')

        # Evaluamos el resultado de la ejecución, y devolvemos una respuesta:
        if result.returncode == 0:
            journal.send("Script 'deleteTrashImage.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script deleteTrashImage.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": True,
                "output": "Image deleted successfully"
            }), 200
        else:
            journal.send(f"Script 'deleteTrashImage.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script deleteTrashImage.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "error": result.stderr
            }), 500
    except Exception as error_description:
        journal.send(f"Script 'deleteTrashImage.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script deleteTrashImage.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "exception": str(error_description)
        }), 500


# ---------------------------------------------------------


# 9 - Endpoint "Importar una Imagen" (ASINCRONO):
@app.route("/ogrepository/v1/repo/images", methods=['POST'])
def import_image():
    """ Este endpoint importa la imagen especificada como primer parámetro (y todos sus archivos asociados), desde un servidor remoto al servidor local.
        Para ello, ejecuta el script "importImage.py", con el nombre de la imagen como primer parámetro,
          la IP o hostname del servidor remoto como segundo parámetro, y el usuario con el que conectar al servidor como tercer parámetro.
    """
    journal.send("Running endpoint 'Importar una Imagen'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    # Almacenamos los parámetros enviados en el JSON:
    json_data = json.loads(request.data)
    image_name = json_data.get("image")
    remote_ip = json_data.get("repo_ip")
    remote_user = json_data.get("user")

    # Comprobamos la conexión con el equipo remoto, y si falla salimos del endpoint, retornando un error:
    connection_OK = check_remote_connection(remote_ip, remote_user)
    if connection_OK == False:
        journal.send("Can't connect to remote server", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'400', 'operation':'Run endpoint import_image', 'desc':'Unable to connect to remote server'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "exception": "Can't connect to remote server"
        }), 400

    # Construimos la ruta de la imagen:
    image_file_path = f"{repo_path}{image_name}"

    # Comprobamos si la imagen remota no existe o está bloqueada, en cuyos casos salimos del endpoint y retornamos el error correspondiente:
    check_image = check_remote_image(remote_ip, remote_user, image_file_path)
    if check_image == "Remote image not found":
        journal.send("Remote image not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint import_image', 'desc':'Warning: Remote image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "exception": "Remote image not found"
        }), 400
    elif check_image == "Remote image is locked":
        journal.send("Remote image is locked", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint import_image', 'desc':'Warning: Remote image is locked'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "exception": "Remote image is locked"
        }), 400

    # Construimos la llamada al script:
    cmd = ['python3', f"{script_path}/importImage.py", image_file_path, remote_ip, remote_user]

    try:
        journal.send("Running script 'importImage.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        # Ejecutamos el script "importImage.py" (con los parámetros almacenados), y almacenamos el resultado (pero sin esperar a que termine el proceso):
        result = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8')

        # Generamos el ID para identificar el trabajo asíncrono:
        job_id = f"TransferImage_{''.join(random.choice('0123456789abcdef') for char in range(8))}"
        journal.send(f"JOB ID generated ({job_id})", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

        # Evaluamos el resultado de la ejecución, y devolvemos una respuesta:
        if result.returncode is None:
            journal.send("Script 'importImage.py' result OK (ReturnCode: None)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script importImage.py', 'desc':'Result OK (ReturnCode: None)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")

            # Si el resultado es correcto, llamamos a la función "check_lock_local" en un hilo paralelo
            #   (para que compruebe si la imagen se ha acabado de importar exitosamente):
            journal.send("Calling function 'check_lock_local'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            threading.Thread(target=check_lock_local, args=(image_file_path, job_id,)).start()

            # Informamos que la imagen se está importando, y salimos del endpoint:
            return jsonify({
                "success": True,
                "output": "Importing image...",
                "job_id": job_id
            }), 200
        else:
            journal.send("Script 'importImage.py' result KO (Image import failed)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script importImage.py', 'desc':'Result KO (Error: Image import failed)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "error": "Image import failed"
            }), 500
    except subprocess.CalledProcessError as error:
        journal.send(f"Script 'importImage.py' result KO (Process Exception: {str(error)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script importImage.py', 'desc':'Result KO (Process Exception: {str(error)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "process exception": str(error)
        }), 500
    except Exception as error_description:
        journal.send(f"Script 'importImage.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script importImage.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "exception": str(error_description)
        }), 500


# ---------------------------------------------------------


# 10 - Endpoint "Hacer backup de una Imagen" (ASINCRONO):
@app.route("/ogrepository/v1/repo/images", methods=['PUT'])
def backup_image():
    """ Este endpoint exporta la imagen especificada como primer parámetro (y todos sus archivos asociados), desde el servidor local a un equipo remoto (que no tiene por qué ser un repositorio).
        Para ello, ejecuta el script "backupImage.py", con el nombre de la imagen como primer parámetro, la IP o hostname del equipo remoto como segundo parámetro,
          el usuario con el que conectar al equipo remoto como tercer parámetro, y la ruta remota en la que copiar la imagen como cuarto parámetro.
    """
    journal.send("Running endpoint 'Hacer backup de una Imagen'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    # Almacenamos los parámetros enviados en el JSON:
    json_data = json.loads(request.data)
    image_id = json_data.get("ID_img")
    remote_ip = json_data.get("repo_ip")
    remote_user = json_data.get("user")
    remote_path = json_data.get("remote_path")

    # Si el úitimo carácter de "remote_path" no es una barra, la añadimos:
    if remote_path[-1] != "/":
        remote_path = f"{remote_path}/"

    # Obtenemos el nombre y la extensión de la imagen:
    param_dict = get_image_params(image_id, "repo")

    # Evaluamos los parámetros obtenidos, para construir la ruta de la imagen, o para devolver un error si no se ha encontrado la imagen (o si está bloqueada):
    if param_dict:
        image_name = f"{param_dict['name']}.{param_dict['extension']}"

        # Comprobamos si el archivo que bloquea la imagen existe, llamando a la función "check_file_exists":
        image_lock_exists = check_file_exists(f"{repo_path}{image_name}.lock")

        # Si la imagen existe pero está bloqueada, devolvemos un error:
        if image_lock_exists == True:
            journal.send("Image is locked", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint backup_image', 'desc':'Warning: Image is locked'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "exception": "Image is locked"
            }), 400
    else:
        journal.send("Image not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint backup_image', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "error": "Image not found"
        }), 400

    # Comprobamos la conexión con el equipo remoto, y si falla salimos del endpoint, retornando un error:
    connection_OK = check_remote_connection(remote_ip, remote_user)

    if connection_OK == False:
        journal.send("Can't connect to remote server", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'400', 'operation':'Run endpoint backup_image', 'desc':'Unable to connect to remote host'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "exception": "Can't connect to remote host"
        }), 400

    # Construimos la llamada al script:
    cmd = ['python3', f"{script_path}/backupImage.py", image_name, remote_ip, remote_user, remote_path]

    try:
        journal.send("Running script 'backupImage.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        # Ejecutamos el script "backupImage.py" (con los parámetros almacenados), y almacenamos el resultado (pero sin esperar a que termine el proceso):
        result = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8')

        # Generamos el ID para identificar el trabajo asíncrono:
        job_id = f"BackupImage_{''.join(random.choice('0123456789abcdef') for char in range(8))}"
        journal.send(f"JOB ID generated ({job_id})", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

        # Evaluamos el resultado de la ejecución, y devolvemos una respuesta:
        if result.returncode is None:
            journal.send("Script 'backupImage.py' result OK (ReturnCode: None)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script backupImage.py', 'desc':'Result OK (ReturnCode: None)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
            # Si el resultado es correcto, llamamos a la función "check_remote_backup" en un hilo paralelo
            #   (para que compruebe si la imagen se ha acabado de copiar exitosamente):
            journal.send("Calling function 'check_remote_backup'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            threading.Thread(target=check_remote_backup, args=(image_name, remote_ip, remote_user, remote_path, job_id,)).start()

            # Informamos que la imagen se está exportando, y salimos del endpoint:
            return jsonify({
                "success": True,
                "output": "Making image backup...",
                "job_id": job_id
            }), 200
        else:
            journal.send("Script 'backupImage.py' result KO (Backup image failed)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script backupImage.py', 'desc':'Result KO (Backup image failed)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "error": "Backup image failed"
            }), 500
    except subprocess.CalledProcessError as error:
        journal.send(f"Script 'backupImage.py' result KO (Process Exception: {str(error)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script backupImage.py', 'desc':'Result KO (Process Exception: {str(error_)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
                "success": False,
                "process exception": str(error)
            }), 500
    except Exception as error_description:
        journal.send(f"Script 'backupImage.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script backupImage.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "exception": str(error_description)
        }), 500


# ---------------------------------------------------------


# 11 - Endpoint "Crear archivos auxiliares" (ASINCRONO):
@app.route("/ogrepository/v1/images/torrentsum", methods=['POST'])
def create_torrent_sum():
    """ Este endpoint crea los archivos ".size", ".sum", ".full.sum" y ".torrent" para la imagen que recibe como parámetro.
        Para ello, ejecuta el script "createTorrentSum.py", con el nombre de la imagen como único parámetro.
    """
    journal.send("Running endpoint 'Crear archivos auxiliares'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    # Almacenamos los parámetros enviados en el JSON:
    json_data = json.loads(request.data)
    image_name = json_data.get("image")

    # Comprobamos si la imagen existe, llamando a la función "check_file_exists":
    image_exists = check_file_exists(f"{repo_path}{image_name}")

    # Si la imagen no existe, retornamos un error y salimos del endpoint:
    if image_exists == False:
        journal.send("Image not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint create_torrent_sum', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "exception": "Image not found"
        }), 400

    # Construimos la ruta de la imagen (relativa a "repo_path"):
    image_file_path = image_name

    # Construimos la llamada al script:
    cmd = ['python3', f"{script_path}/createTorrentSum.py", image_file_path]

    try:
        journal.send("Running script 'createTorrentSum.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        # Ejecutamos el script "createTorrentSum.py" (con los parámetros almacenados), y almacenamos el resultado (pero sin esperar a que termine el proceso):
        result = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8')

        # Generamos el ID para identificar el trabajo asíncrono:
        job_id = f"CreateAuxiliarFiles_{''.join(random.choice('0123456789abcdef') for char in range(8))}"
        journal.send(f"JOB ID generated ({job_id})", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

        # Evaluamos el resultado de la ejecución, y devolvemos una respuesta:
        if result.returncode is None:
            journal.send("Script 'createTorrentSum.py' result OK (ReturnCode: None)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script createTorrentSum.py', 'desc':'Result OK (ReturnCode: None)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
            # Si el resultado es correcto, llamamos a la función "check_aux_files" en un hilo paralelo
            #   (para que compruebe si se han creado todos los archivos auxiliares exitosamente):
            journal.send("Calling function 'check_aux_files'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            threading.Thread(target=check_aux_files, args=(f"{repo_path}{image_file_path}", job_id,)).start()

            # Informamos que los archivos auxiliares se están creando, y salimos del endpoint:
            return jsonify({
                "success": True,
                "output": "Creating auxiliar files...",
                "job_id": job_id
            }), 200
        else:
            journal.send(f"Script 'createTorrentSum.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script createTorrentSum.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "error": result.stderr
            }), 500
    except Exception as error_description:
        journal.send(f"Script 'createTorrentSum.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script createTorrentSum.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "exception": str(error_description)
        }), 500


# ---------------------------------------------------------


# 12 - Endpoint "Enviar paquete Wake On Lan" (SINCRONO):
@app.route("/ogrepository/v1/wol", methods=['POST'])
def send_wakeonlan():
    """ Este endpoint envía un paquete mágico Wake On Lan a la dirección MAC especificada, a través de la IP de broadcast especificadac.
        Para ello, ejecuta el script "sendWakeOnLan.py", con la IP de broadcast como primer parámetro, y la MAC como segundo parámetro.
    """
    journal.send("Running endpoint 'Enviar paquete Wake On Lan'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    # Almacenamos los parámetros enviados en el JSON:
    json_data = json.loads(request.data)
    broadcast_ip = json_data.get("broadcast_ip")
    mac = json_data.get("mac")

    try:
        journal.send("Running script 'sendWakeOnLan.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        # Ejecutamos el script "sendWakeOnLan.py" (con los parámetros almacenados), y almacenamos el resultado:
        result = subprocess.run(['python3', f"{script_path}/sendWakeOnLan.py", broadcast_ip, mac], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8')

        # Evaluamos el resultado de la ejecución, y devolvemos una respuesta:
        if result.returncode == 0:
            journal.send("Script 'sendWakeOnLan.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script sendWakeOnLan.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": True,
                "output": "Wake On Lan packet sended successfully"
            }), 200
        else:
            journal.send(f"Script 'sendWakeOnLan.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script sendWakeOnLan.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "error": result.stderr
            }), 500
    except Exception as error_description:
        journal.send(f"Script 'sendWakeOnLan.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script sendWakeOnLan.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "exception": str(error_description)
        }), 500


# ---------------------------------------------------------


# 13 - Endpoint "Enviar una Imagen mediante UDPcast" (ASINCRONO):
@app.route("/ogrepository/v1/udpcast", methods=['POST'])
def send_udpcast():
    """ Este endpoint envía mediante UDPcast la imagen que recibe como primer parámetro, con los datos de transferencia que recibe en los demás parámetros.
        Para ello, ejecuta el script "sendFileMcast.py", con la imagen como primer parámetro, y los demás en una cadena (como segundo parámetro).
    """
    journal.send("Running endpoint 'Enviar una Imagen mediante UDPcast'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    # Almacenamos los parámetros enviados en el JSON:
    json_data = json.loads(request.data)
    image_id = json_data.get("ID_img")
    port = json_data.get("port")
    method = json_data.get("method")
    ip = json_data.get("ip")
    bitrate = json_data.get("bitrate")
    nclients = json_data.get("nclients")
    maxtime = json_data.get("maxtime")

    # Obtenemos el nombre y la extensión de la imagen:
    param_dict = get_image_params(image_id, "repo")

    # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devolver un error si no se ha encontrado la imagen:
    if param_dict:
        cmd = ['python3', f"{script_path}/sendFileMcast.py", f"{param_dict['name']}.{param_dict['extension']}", f"{port}:{method}:{ip}:{bitrate}:{nclients}:{maxtime}"]
    else:
        journal.send("Image not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint send_udpcast', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "error": "Image not found"
        }), 400

    try:
        journal.send("Running script 'sendFileMcast.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        # Ejecutamos el script "sendFileMcast.py" (con los parámetros almacenados), y almacenamos el resultado (pero sin esperar a que termine el proceso):
        result = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8')

        # Comprobamos si está corriendo el proceso correspondiente de "udp-sender" (esperando 5 segundos para darle tiempo a iniciarse):
        sleep(5)
        process_running = search_process('udp-sender', f"{param_dict['name']}.{param_dict['extension']}")

        # Evaluamos el resultado de la ejecución, y devolvemos una respuesta:
        if result.returncode is None and process_running == True:
            journal.send("Script 'sendFileMcast.py' result OK (ReturnCode: None), and process running", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script sendFileMcast.py', 'desc':'Result OK (ReturnCode: None), and process running'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": True,
                "output": "Sending image..."
            }), 200
        else:
            journal.send("Script 'sendFileMcast.py' result KO (Image send failed)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script sendFileMcast.py', 'desc':'Result KO (Image send failed)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "error": "Image send failed"
            }), 500
    except subprocess.CalledProcessError as error:
        journal.send(f"Script 'sendFileMcast.py' result KO (Process Exception: {str(error)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script sendFileMcast.py', 'desc':'Result KO (Process Exception: {str(error)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
                "success": False,
                "process exeption": str(error)
            }), 500
    except Exception as error_description:
        journal.send(f"Script 'sendFileMcast.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script sendFileMcast.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "exception": str(error_description)
        }), 500


# ---------------------------------------------------------


# 14 - Endpoint "Enviar una Imagen mediante UFTP" (ASINCRONO):
@app.route("/ogrepository/v1/uftp", methods=['POST'])
def send_uftp():
    """ Este endpoint envía mediante UFTP la imagen que recibe como primer parámetro, con los datos de transferencia que recibe en los demás parámetros.
        Para ello, ejecuta el script "sendFileUFTP.py", con la imagen como primer parámetro, y los demás en una cadena (como segundo parámetro).
        NOTA: Es necesario que los clientes se hayan puesto en escucha previamente (ejecutando el script "listenUFTPD.py").
    """
    journal.send("Running endpoint 'Enviar una Imagen mediante UFTP'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    # Almacenamos los parámetros enviados en el JSON:
    json_data = json.loads(request.data)
    image_id = json_data.get("ID_img")
    port = json_data.get("port")
    ip = json_data.get("ip")
    bitrate = json_data.get("bitrate")

    # Obtenemos el nombre y la extensión de la imagen:
    param_dict = get_image_params(image_id, "repo")

    # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devolver un error si no se ha encontrado la imagen:
    if param_dict:
        cmd = ['python3', f"{script_path}/sendFileUFTP.py", f"{param_dict['name']}.{param_dict['extension']}", f"{port}:{ip}:{bitrate}"]
    else:
        journal.send("Image not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint send_uftp', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "error": "Image not found"
        }), 400

    try:
        journal.send("Running script 'sendFileUFTP.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        # Ejecutamos el script "sendFileUFTP.py" (con los parámetros almacenados), y almacenamos el resultado (pero sin esperar a que termine el proceso):
        result = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8')

        # Comprobamos si está corriendo el proceso correspondiente de "uftp" (esperando 5 segundos para darle tiempo a iniciarse):
        sleep(5)
        process_running = search_process('uftp', f"{param_dict['name']}.{param_dict['extension']}")

        # Evaluamos el resultado de la ejecución, y devolvemos una respuesta:
        if result.returncode is None and process_running == True:
            journal.send("Script 'sendFileUFTP.py' result OK (ReturnCode: None), and process running", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script sendFileUFTP.py', 'desc':'Result OK (ReturnCode: None), and process running'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": True,
                "output": "Sending image..."
            }), 200
        else:
            journal.send("Script 'sendFileUFTP.py' result KO (Image send failed)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script sendFileUFTP.py', 'desc':'Result KO (Image send failed)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "error": "Image send failed"
            }), 500
    except subprocess.CalledProcessError as error:
        journal.send(f"Script 'sendFileUFTP.py' result KO (Process Exception: {str(error)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script sendFileUFTP.py', 'desc':'Result KO (Process Exception: {str(error)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
                "success": False,
                "process exeption": str(error)
            }), 500
    except Exception as error_description:
        journal.send(f"Script 'sendFileUFTP.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script sendFileUFTP.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "exception": str(error_description)
        }), 500


# ---------------------------------------------------------


# 15 - Endpoint "Enviar una Imagen mediante P2P" (ASINCRONO):
@app.route("/ogrepository/v1/p2p", methods=['POST'])
def send_p2p():
    """ Este endpoint inicia el tracker "bttrack" y el seeder "bittornado", en el directorio de imágenes (sirviendo todas las imágenes).
        Para ello, ejecuta los scripts "runTorrentTracker.py" y "runTorrentSeeder.py", que no reciben parámetros.
    """
    journal.send("Running endpoint 'Enviar una Imagen mediante P2P'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    # Almacenamos los parámetros enviados en el JSON:
    json_data = json.loads(request.data)
    image_id = json_data.get("ID_img")

    # Obtenemos el nombre y la extensión de la imagen:
    param_dict = get_image_params(image_id, "repo")

    # Evaluamos los parámetros obtenidos, para construir las llamadas a los scripts, o para devolver un error si no se ha encontrado la imagen:
    if param_dict:
        cmd_tracker = ['sudo', 'python3', f"{script_path}/runTorrentTracker.py"] # Este script si que requiere ser ejecutado con "sudo"
        cmd_seeder = ['sudo', 'python3', f"{script_path}/runTorrentSeeder.py"] # Este script si que requiere ser ejecutado con "sudo"
        base_path = repo_path.rstrip('/') # Le quito la última barra para poder buscar correctamente en los procesos
    else:
        journal.send("Image not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint send_p2p', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "error": "Image not found"
        }), 400

    # Ejecutamos los scripts "runTorrentSeeder.py" y "runTorrentSeeder.py", que no reciben parámetros.
    # NOTA: No almacenamos la salida ni comprobamos los errores, porque los procesos quedarán corriendo hasta que se finalicen manualmente,
    #         por lo que no podemos comprobar el returncode (luego comprobaremos si los procesos se han iniciado correctamente).
    journal.send("Running script 'runTorrentTracker.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
    subprocess.Popen(cmd_tracker)

    journal.send("Running script 'runTorrentSeeder.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
    subprocess.Popen(cmd_seeder)

    # Comprobamos si el tracker y el seeder están corriendo, y si apuntan al directorio que le hemos pasado
    #   (esperamos 10 segundos antes de hacerlo, porque los procesos no se inician inmediatamente):
    sleep(10)
    tracker_running = search_process('bttrack', base_path)
    seeder_running = search_process('btlaunchmany', base_path)

    # Evaluamos las comprobaciones anteriores, para devolver la respuesta que corresponda:
    if tracker_running and seeder_running:
        journal.send("Scripts 'runTorrentTracker.py' and 'runTorrentSeeder.py' results OK (ReturnCodes: None), and processes running", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run scripts runTorrentTracker.py and runTorrentSeeder.py', 'desc':'Results OK (ReturnCodes: None), and processes running'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": True,
            "output": "Tracker and Seeder serving image correctly"
        }), 200
    else:
        journal.send("Scripts 'runTorrentTracker.py' and 'runTorrentSeeder.py' results KO (Tracker or/and Seeder not runnig)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run scripts runTorrentTracker.py and runTorrentSeeder.py', 'desc':'Results KO (Tracker or/and Seeder not runnig)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "error": "Tracker or Seeder (or both) not running"
        }), 500


# ---------------------------------------------------------


# 16 - Endpoint "Ver Estado de Transmisiones UDPcast" (SINCRONO):
@app.route("/ogrepository/v1/udpcast", methods=['GET'])
def get_udpcast_info():
    """ Este endpoint devuelve información sobre los procesos de "udp-sender" activos, en formato json,
          lo que en la práctica permite comprobar las transferencias UDPcast activas.
        Para ello, ejecuta el script "getUDPcastInfo.py", que no recibe parámetros.
    """
    journal.send("Running endpoint 'Ver Estado de Transmisiones UDPcast'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    try:
        journal.send("Running script 'getUDPcastInfo.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        # Ejecutamos el script "getUDPcastInfo.py", y almacenamos el resultado:
        result = subprocess.run(['python3', f"{script_path}/getUDPcastInfo.py"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8')

        # Evaluamos el resultado de la ejecución, y devolvemos la respuesta:
        if result.returncode == 0:
            journal.send("Script 'getUDPcastInfo.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script getUDPcastInfo.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": True,
                "output": json.loads(result.stdout)
            }), 200
        else:
            journal.send(f"Script 'getUDPcastInfo.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script getUDPcastInfo.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "error": result.stderr
            }), 500
    except Exception as error_description:
        if "exit status 1" in str(error_description):
            journal.send("No UDPcast active transmissions", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run script getUDPcastInfo.py', 'desc':'Warning: No UDPcast active transmissions'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "exception": "No UDPCast active transmissions"
            }), 400
        else:
            journal.send(f"Script 'getUDPcastInfo.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script getUDPcastInfo.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "exception": str(error_description)
            }), 500


# ---------------------------------------------------------


# 17 - Endpoint "Ver Estado de Transmisiones UFTP" (SINCRONO):
@app.route("/ogrepository/v1/uftp", methods=['GET'])
def get_uftp_info():
    """ Este endpoint devuelve información sobre los procesos de "uftp" activos, en formato json,
          lo que en la práctica permite comprobar las transferencias UFTP activas.
        Para ello, ejecuta el script "getUFTPInfo.py", que no recibe parámetros.
    """
    journal.send("Running endpoint 'Ver Estado de Transmisiones UFTP'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    try:
        journal.send("Running script 'getUFTPInfo.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        # Ejecutamos el script "getUFTPInfo.py", y almacenamos el resultado:
        result = subprocess.run(['python3', f"{script_path}/getUFTPInfo.py"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8')

        # Evaluamos el resultado de la ejecución, y devolvemos la respuesta:
        if result.returncode == 0:
            journal.send("Script 'getUFTPInfo.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script getUFTPInfo.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": True,
                "output": json.loads(result.stdout)
            }), 200
        else:
            journal.send(f"Script 'getUFTPInfo.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script getUFTPInfo.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "error": result.stderr
            }), 500
    except Exception as error_description:
        if "exit status 1" in str(error_description):
            journal.send("No UFTP active transmissions", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run script getUFTPInfo.py', 'desc':'Warning: No UFTP active transmissions'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "exception": "No UFTP active transmissions"
            }), 400
        else:
            journal.send(f"Script 'getUFTPInfo.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script getUFTPInfo.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "exception": str(error_description)
            }), 500


# ---------------------------------------------------------


# 18 - Endpoint "Cancelar Transmisión UDPcast" (SINCRONO):
@app.route("/ogrepository/v1/udpcast/images/<string:imageId>", methods=['DELETE'])
def stop_udpcast(imageId):
    """ Este endpoint cancela la transmisión UDPcast de la imagen que recibe como parámetro, finalizando el proceso "udp-sender" asociado.
        Para ello, ejecuta el script "stopUDPcast.py", pasándole el nombre de la imagen.
    """
    journal.send("Running endpoint 'Cancelar Transmisión UDPcast'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    # Obtenemos el nombre y la extensión de la imagen:
    param_dict = get_image_params(imageId, "repo")

    # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devolver un error si no se ha encontrado la imagen:
    if param_dict:
        cmd = ['python3', f"{script_path}/stopUDPcast.py", f"{param_dict['name']}.{param_dict['extension']}"]
    else:
        journal.send("Image not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint stop_udpcast', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "error": "Image not found"
        }), 400

    try:
        journal.send("Running script 'stopUDPcast.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        # Ejecutamos el script "stopUDPcast.py" (con los parámetros almacenados), y almacenamos el resultado:
        result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8')

        # Evaluamos el resultado de la ejecución, y devolvemos una respuesta:
        if result.returncode == 0:
            journal.send("Script 'stopUDPcast.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script stopUDPcast.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": True,
                "output": "Image transmission canceled successfully"
            }), 200
        else:
            journal.send(f"Script 'stopUDPcast.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script stopUDPcast.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "error": result.stderr
            }), 500
    except Exception as error_description:
        if "exit status 3" in str(error_description):
            journal.send("No UDPCast active transmissions for specified image", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run script stopUDPcast.py', 'desc':'Warning: No UDPCast active transmissions for specified image'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "exception": "No UDPCast active transmissions for specified image"
            }), 400
        elif "exit status 4" in str(error_description):
            journal.send("Script 'stopUDPcast.py' result KO (Unexpected error checking UDPcast transmissions)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script stopUDPcast.py', 'desc':'Result KO (Unexpected error checking UDPcast transmissions)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "exception": "Unexpected error checking UDPcast transmissions"
            }), 500
        elif "exit status 5" in str(error_description):
            journal.send("Script 'stopUDPcast.py' result KO (Unexpected error finalizing UDPcast transmission)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script stopUDPcast.py', 'desc':'Result KO (Unexpected error finalizing UDPcast transmission)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "exception": "Unexpected error finalizing UDPcast transmission"
            }), 500
        else:
            journal.send(f"Script 'stopUDPcast.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script stopUDPcast.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "exception": str(error_description)
            }), 500


# ---------------------------------------------------------


# 19 - Endpoint "Cancelar Transmisión UFTP" (SINCRONO):
@app.route("/ogrepository/v1/uftp/images/<string:imageId>", methods=['DELETE'])
def stop_uftp(imageId):
    """ Este endpoint cancela la transmisión UFTP de la imagen que recibe como parámetro, finalizando el proceso "uftp" asociado.
        Para ello, ejecuta el script "stopUFTP.py", pasándole el nombre de la imagen.
    """
    journal.send("Running endpoint 'Cancelar Transmisión UFTP'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    # Obtenemos el nombre y la extensión de la imagen:
    param_dict = get_image_params(imageId, "repo")

    # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devolver un error si no se ha encontrado la imagen:
    if param_dict:
        cmd = ['python3', f"{script_path}/stopUFTP.py", f"{param_dict['name']}.{param_dict['extension']}"]
    else:
        journal.send("Image not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint stop_uftp', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "error": "Image not found"
        }), 400

    try:
        journal.send("Running script 'stopUFTP.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        # Ejecutamos el script "stopUFTP.py" (con los parámetros almacenados), y almacenamos el resultado:
        result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8')

        # Evaluamos el resultado de la ejecución, y devolvemos una respuesta:
        if result.returncode == 0:
            journal.send("Script 'stopUFTP.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script stopUFTP.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": True,
                "output": "Image transmission canceled successfully"
            }), 200
        else:
            journal.send(f"Script 'stopUFTP.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script stopUFTP.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "error": result.stderr
            }), 500
    except Exception as error_description:
        if "exit status 3" in str(error_description):
            journal.send("No UFTP active transmissions for specified image", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run script stopUFTP.py', 'desc':'Warning: No UFTP active transmissions for specified image'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "exception": "No UFTP active transmissions for specified image"
            }), 400
        elif "exit status 4" in str(error_description):
            journal.send("Script 'stopUFTP.py' result KO (Unexpected error checking UFTP transmissions)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script stopUFTP.py', 'desc':'Result KO (Unexpected error checking UFTP transmissions)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "exception": "Unexpected error checking UFTP transmissions"
            }), 500
        elif "exit status 5" in str(error_description):
            journal.send("Script 'stopUFTP.py' result KO (Unexpected error finalizing UFTP transmission)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script stopUFTP.py', 'desc':'Result KO (Unexpected error finalizing UFTP transmission)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "exception": "Unexpected error finalizing UFTP transmission"
            }), 500
        else:
            journal.send(f"Script 'stopUFTP.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script stopUFTP.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "exception": str(error_description)
            }), 500


# ---------------------------------------------------------


# 20 - Endpoint "Cancelar Transmisiones P2P" (SINCRONO):
@app.route("/ogrepository/v1/p2p", methods=['DELETE'])
def stop_p2p():
    """ Este endpoint cancela las transmisiones P2P activas, finalizando los procesos "btlaunchmany.bittornado" (seeder) y "bttrack" (tracker).
        Para ello, ejecuta el script "stopP2P.py", que no recibe parámetros.
    """
    journal.send("Running endpoint 'Cancelar Transmisiones P2P'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    try:
        journal.send("Running script 'stopP2P.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        # Ejecutamos el script "stopP2P.py", y almacenamos el resultado (este script si que requiere ser ejecutado con "sudo"):
        result = subprocess.run(['sudo', 'python3', f"{script_path}/stopP2P.py"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8')

        # Evaluamos el resultado de la ejecución, y devolvemos la respuesta:
        if result.returncode == 0:
            journal.send("Script 'stopP2P.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script stopP2P.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": True,
                "output": "P2P transmissions canceled successfully"
            }), 200
        else:
            journal.send(f"Script 'stopP2P.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script stopP2P.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "error": result.stderr
            }), 500
    except Exception as error_description:
        journal.send(f"Script 'stopP2P.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script stopP2P.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "exception": str(error_description)
        }), 500


# ---------------------------------------------------------


# 21 - Endpoint "Convertir imagen virtual a imagen OpenGnsys" (ASINCRONO):
@app.route("/ogrepository/v1/images/virtual", methods=['POST'])
def convert_virtual_image():
    """ Este endpoint convierte la imagen virtual especificada como parámetro en una imagen "img" como las que se generan desde OpenGnsys
          (con "partclone" y "lzop"), por lo que luego puede ser restaurada como cualquier otra imagen del repositorio.
        Para ello, ejecuta el script "convertVMtoIMG.py", con el nombre de la imagen virtual como primer parámetro,
          y el sistema de archivos de la partición a clonar (en formato "blkid") como segundo parámetro.
    """
    journal.send("Running endpoint 'Convertir imagen virtual a imagen OpenGnsys'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    # Almacenamos los parámetros enviados en el JSON, y extraemos el nombre y la extensión:
    json_data = json.loads(request.data)
    vm_image_name_full = json_data.get("virtual_image")
    vm_image_name = vm_image_name_full.split('.')[0]
    vm_extension = vm_image_name_full.split('.')[1]
    filesystem = json_data.get("filesystem").lower()

    # Comprobamos si existe la imagen virtual, llamando a la función "check_file_exists":
    vm_image_exists = check_file_exists(f"{vm_path}{vm_image_name_full}")

    # Si la imagen virtual no existe, devolvemos un error:
    if vm_image_exists == False:
        journal.send("Virtual image not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint convert_virtual_image', 'desc':'Warning: Virtual image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "exception": "Virtual image not found"
        }), 400

    # Comprobamos si ya existe una imagen "img" con el mismo nombre que la imagen virtual, llamando a la función "check_file_exists":
    img_image_exists = check_file_exists(f"{repo_path}{vm_image_name}.img")

    # Si existe una imagen con el mismo nombre que la imagen virtual (salvo por la extensión), devolvemos un error:
    if img_image_exists == True:
        journal.send("There is an image with the same name as the virtual image", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint convert_virtual_image', 'desc':'Warning: There is an image with the same name as the virtual image'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "exception": "There is an image with the same name as the virtual image"
        }), 400

    # Comprobamos si hay espacio suficiente en disco para convertir la imagen virtual (4 veces su tamaño):
    enough_free_space = check_free_space(vm_image_name_full, vm_path)

    # Si no hay suficiente espacio libre en disco, devolvemos un error:
    if enough_free_space == False:
        journal.send("There is not enough free disk space", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint convert_virtual_image', 'desc':'Warning: There is not enough free disk space'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "exception": "There is not enough free disk space"
        }), 400

    # Construimos la llamada al script:
    cmd = ['python3', f"{script_path}/convertVMtoIMG.py", vm_image_name_full, filesystem]

    try:
        journal.send("Running script 'convertVMtoIMG.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        # Ejecutamos el script "convertVMtoIMG.py" (con los parámetros almacenados), y almacenamos el resultado (pero sin esperar a que termine el proceso):
        result = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8')

        # Generamos el ID para identificar el trabajo asíncrono:
        job_id = f"ConvertImageFromVirtual_{''.join(random.choice('0123456789abcdef') for char in range(8))}"
        journal.send(f"JOB ID generated ({job_id})", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

        # Evaluamos el resultado de la ejecución, y devolvemos una respuesta:
        if result.returncode is None:
            journal.send("Script 'convertVMtoIMG.py' result OK (ReturnCode: None)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script convertVMtoIMG.py', 'desc':'Result OK (ReturnCode: None)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")

            # Si el resultado es correcto, llamamos a la función "check_virtual_image_conversion" en un hilo paralelo
            #   (para que compruebe si la imagen se ha acabado de convertir exitosamente):
            journal.send("Calling function 'check_virtual_image_conversion'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            threading.Thread(target=check_virtual_image_conversion, args=(vm_image_name, job_id,)).start()

            # Informamos que la imagen se está convirtiendo, y salimos del endpoint:
            return jsonify({
                "success": True,
                "output": "Converting virtual image...",
                "job_id": job_id
            }), 200
        else:
            journal.send("Script 'convertVMtoIMG.py' result KO (Virtual image conversion failed)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script convertVMtoIMG.py', 'desc':'Result KO (Error: Virtual image conversion failed)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "error": "Virtual image conversion failed"
            }), 500
    except subprocess.CalledProcessError as error:
        journal.send(f"Script 'convertVMtoIMG.py' result KO (Process Exception: {str(error)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script convertVMtoIMG.py', 'desc':'Result KO (Process Exception: {str(error)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "process exception": str(error)
        }), 500
    except Exception as error_description:
        journal.send(f"Script 'convertVMtoIMG.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script convertVMtoIMG.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "exception": str(error_description)
        }), 500


# ---------------------------------------------------------


# 22 - Endpoint "Convertir imagen OpenGnsys a imagen virtual" (ASINCRONO):
@app.route("/ogrepository/v1/images/virtual", methods=['PUT'])
def convert_image_to_virtual():
    """ Este endpoint convierte la imagen de OpenGnsys especificada como parámetro en una imagen virtual con la extensión especificada (".vdi", ".vmdk", etc).
        Para ello, ejecuta el script "convertIMGtoVM.py", con el nombre de la imagen "img" como primer parámetro,
          y la extensión del disco virtual destino (sin punto) como segundo parámetro.
    """
    journal.send("Running endpoint 'Convertir imagen OpenGnsys a imagen virtual'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

    # Almacenamos los parámetros enviados en el JSON,:
    json_data = json.loads(request.data)
    image_id = json_data.get("ID_img")
    vm_extension = json_data.get("vm_extension").lower().lstrip('.')

    # Obtenemos el nombre y la extensión de la imagen:
    param_dict = get_image_params(image_id, "repo")

    # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devolver un error si no se ha encontrado la imagen:
    if param_dict:
        image_name_full = f"{param_dict['name']}.{param_dict['extension']}"
        image_name = f"{param_dict['name']}"
        cmd = ['python3', f"{script_path}/convertIMGtoVM.py", image_name_full, vm_extension]
    else:
        journal.send("Image not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint convert_image_to_virtual', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "error": "Image not found"
        }), 400

    # Comprobamos si ya existe una imagen virtual exportada con el mismo nombre que la imagen "img" y la extensión especificada, llamando a la función "check_file_exists":
    vm_image_exists = check_file_exists(f"{vm_path}export/{image_name}.{vm_extension}")

    # Si existe una imagen con el mismo nombre que la imagen virtual (salvo por la extensión), devolvemos un error:
    if vm_image_exists == True:
        journal.send("There is an exported virtual image with the same name as the image", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint convert_image_to_virtual', 'desc':'Warning: There is an exported virtual image with the same name as the image'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "exception": "There is an exported virtual image with the same name as the image"
        }), 400

    # Comprobamos si hay espacio suficiente en disco para convertir la imagen "img" a virtual (4 veces su tamaño):
    enough_free_space = check_free_space(image_name_full, repo_path)

    # Si no hay suficiente espacio libre en disco, devolvemos un error:
    if enough_free_space == False:
        journal.send("There is not enough free disk space", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint convert_image_to_virtual', 'desc':'Warning: There is not enough free disk space'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "exception": "There is not enough free disk space"
        }), 400

    try:
        journal.send("Running script 'convertIMGtoVM.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        # Ejecutamos el script "convertIMGtoVM.py" (con los parámetros almacenados), y almacenamos el resultado (pero sin esperar a que termine el proceso):
        result = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8')

        # Generamos el ID para identificar el trabajo asíncrono:
        job_id = f"ConvertImageToVirtual_{''.join(random.choice('0123456789abcdef') for char in range(8))}"
        journal.send(f"JOB ID generated ({job_id})", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")

        # Evaluamos el resultado de la ejecución, y devolvemos una respuesta:
        if result.returncode is None:
            journal.send("Script 'convertIMGtoVM.py' result OK (ReturnCode: None)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script convertIMGtoVM.py', 'desc':'Result OK (ReturnCode: None)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")

            # Si el resultado es correcto, llamamos a la función "check_virtual_image_reconversion" en un hilo paralelo
            #   (para que compruebe si la imagen se ha acabado de convertir exitosamente):
            journal.send("Calling function 'check_virtual_image_reconversion'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            threading.Thread(target=check_virtual_image_reconversion, args=(image_name, vm_extension, job_id,)).start()

            # Informamos que la imagen se está convirtiendo, y salimos del endpoint:
            return jsonify({
                "success": True,
                "output": "Converting image to virtual...",
                "job_id": job_id
            }), 200
        else:
            journal.send("Script 'convertIMGtoVM.py' result KO (Image conversion to virtual failed)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
            journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script convertIMGtoVM.py', 'desc':'Result KO (Error: Image conversion to virtual failed)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
            return jsonify({
                "success": False,
                "error": "Image conversion to virtual failed"
            }), 500
    except subprocess.CalledProcessError as error:
        journal.send(f"Script 'convertIMGtoVM.py' result KO (Process Exception: {str(error)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script convertIMGtoVM.py', 'desc':'Result KO (Process Exception: {str(error)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "process exception": str(error)
        }), 500
    except Exception as error_description:
        journal.send(f"Script 'convertIMGtoVM.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
        journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script convertIMGtoVM.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
        return jsonify({
            "success": False,
            "exception": str(error_description)
        }), 500


