import subprocess

from engine.DiskLib import *
from engine.FileLib import *
from engine.NetLib import *

def ogCreateImageSyntax(*args):
    FUNCNAME=ogCreateImageSyntax.__name__
    # Si se solicita, mostrar ayuda.
    if "help" in args:
        ogHelp(FUNCNAME, f"{FUNCNAME} path_device path_imagefile [str_tool] [str_compressionlevel]", 
                f"{FUNCNAME} /dev/sda1 /opt/opengnsys/images/prueba.img partclone lzop", 
                f"{FUNCNAME} /dev/sda1 /opt/opengnsys/images/prueba.img")
        return

    # Error si no se reciben entre 2 y 4 parámetros.
    if not (2 <= len(args) <= 4):
        ogRaiseError(OG_ERR_FORMAT, " ".join(args))
        return

    # Asignación de parámetros.
    DEV = args[0]
    IMGFILE = args[1]
    if len(args) == 2:
        # Sintaxis por defecto OG DEV IMGFILE
        TOOL = "partclone"
        LEVEL = "gzip"
    elif len(args) == 4:
        # Sintaxis condicionada.
        TOOL = args[2].lower()
        LEVEL = args[3].lower()

    if TOOL == "ntfsclone":
        PARAM1 = f"ntfsclone --force --save-image -O - {DEV}"
    elif TOOL in ["partimage", "default"]:
        PARAM1 = f"partimage -M -f3 -o -d -B gui=no -c -z0 --volume=0 save {DEV} stdout"
    elif TOOL == "partclone":
        FS = ogGetFsType(ogDevToDisk(DEV))
        if FS == "EXT[234]":
            PARAM1 = "partclone.extfs"
        elif FS == "BTRFS":
            PARAM1 = "partclone.btrfs"
        elif FS == "REISERFS":
            PARAM1 = "partclone.reiserfs"
        elif FS == "REISER4":
            PARAM1 = "partclone.reiser4"
        elif FS == "JFS":
            PARAM1 = "partclone.jfs"
        elif FS == "XFS":
            PARAM1 = "partclone.xfs"
        elif FS == "F2FS":
            PARAM1 = "partclone.f2fs"
        elif FS == "NILFS2":
            PARAM1 = "partclone.nilfs2"
        elif FS == "NTFS":
            PARAM1 = "partclone.ntfs"
        elif FS == "EXFAT":
            PARAM1 = "partclone.exfat"
        elif FS in ["FAT16", "FAT32"]:
            PARAM1 = "partclone.fat"
        elif FS in ["HFS", "HFSPLUS"]:
            PARAM1 = "partclone.hfsp"
        elif FS == "UFS":
            PARAM1 = "partclone.ufs"
        elif FS == "VMFS":
            PARAM1 = "partclone.vmfs"
        else:
            PARAM1 = "partclone.imager"
        
        # Por compatibilidad, si no existe el ejecutable usar por defecto "parclone.dd".
        if not subprocess.run(["which", PARAM1], capture_output=True).returncode:
            PARAM1 = "partclone.dd"
        
        PARAM1 = f"{PARAM1} -d0 -F -c -s {DEV}"
        # Algunas versiones de partclone.dd no tienen opción "-c".
        if "-c" not in subprocess.run([PARAM1.split()[0], "--help"], capture_output=True).stdout.decode():
            PARAM1 = PARAM1.replace(" -c ", " ")

    # Comprobar que existe mbuffer.
    if not subprocess.run(["which", "mbuffer"], capture_output=True).returncode:
        PARAM2 = "| mbuffer -q -m 40M "
    else:
        PARAM2 = " "

    # Nivel de compresion.
    if LEVEL in ["0", "none"]:
        PARAM3 = " > "
    elif LEVEL in ["1", "lzop"]:
        PARAM3 = " | lzop > "
    elif LEVEL in ["2", "gzip"]:
        PARAM3 = " | gzip -c > "
    elif LEVEL in ["3", "bzip"]:
        PARAM3 = " | bzip -c > "

    # Sintaxis final.
    if PARAM1:
        return f"{PARAM1} {PARAM2} {PARAM3} {IMGFILE}"

    return ogCreateImageSyntax

def ogRestoreImageSyntax(*args):
    FUNCNAME=ogRestoreImageSyntax.__name__
    TOOL = ""
    COMPRESSOR = ""
    LEVEL = ""
    PART = ""
    IMGFILE = ""
    FILEHEAD = ""
    INFOIMG = ""

    # Si se solicita, mostrar ayuda.
    if "help" in args:
        ogHelp(FUNCNAME, f"{FUNCNAME} filename partition [tool] [levelcompresor]",
                f"{FUNCNAME} /opt/opengnsys/images/prueba.img /dev/sda1 [partclone] [lzop]")
        return

    # Error si no se reciben entre 2 y 4 parámetros.
    if not (2 <= len(args) <= 4):
        ogRaiseError(OG_ERR_FORMAT, " ".join(args))
        return

    # controlamos que el parametro 1 (imagen) es tipo file.
    if not os.path.isfile(args[0]):
        ogRaiseError(OG_ERR_NOTFOUND, args[0])
        return

    # Si 2 parametros (file-origen-, device-destino-) = ogGetImageFull($1)
    if len(args) == 2:
        IMGFILE = args[0]
        PART = args[1]
        INFOIMG = ogGetImageInfo(IMGFILE)
        if INFOIMG:
            TOOL = INFOIMG.split(":")[0]
            COMPRESSOR = INFOIMG.split(":")[1]
            return ogRestoreImageSyntax(IMGFILE, PART, TOOL, COMPRESSOR)

    # Si cuatro parametros genera sintaxis
    if len(args) == 4:
        IMGFILE = args[0]
        PART = args[1]
        # comprobamos parametro herramienta compresion.
        TOOL = args[2].lower()
        # comprobar parámetro compresor.
        LEVEL = args[3].lower()

        if LEVEL in ["0", "none"]:
            COMPRESSOR = " "
        elif LEVEL in ["1", "lzop"]:
            COMPRESSOR = " lzop -dc "
        elif LEVEL in ["2", "gzip"]:
            COMPRESSOR = " gzip -dc "
        elif LEVEL in ["3", "bzip"]:
            COMPRESSOR = " bzip -dc "
        else:
            ogRaiseError(OG_ERR_NOTFOUND, f"Compressor no valid {TOOL}")
            return

        # comprobar mbuffer
        if shutil.which("mbuffer"):
            MBUFFER = "| mbuffer -q -m 40M "
        else:
            MBUFFER = " "

        if TOOL == "ntfsclone":
            TOOL = f"| ntfsclone --restore-image --overwrite {PART} -"
        elif TOOL == "partimage":
            TOOL = f"| partimage -f3 -B gui=no restore {PART} stdin"
        elif TOOL.startswith("partclone"):
            TOOL = f"| partclone.restore -d0 -C -I -o {PART}"
        elif TOOL == "dd":
            TOOL = f"| pv | dd conv=sync,noerror bs=1M of={PART}"
        else:
            ogRaiseError(OG_ERR_NOTFOUND, f"Tools imaging no valid {TOOL}")
            return

        return f"{COMPRESSOR} {IMGFILE} {MBUFFER} {TOOL}"
    
def ogCreateDiskImage(*args):
    FUNCNAME = ogCreateDiskImage.__name__
    # Variables locales
    DISK = ""
    PROGRAM = ""
    IMGDIR = ""
    IMGFILE = ""
    IMGTYPE = ""
    ERRCODE = ""

    # Si se solicita, mostrar ayuda.
    if "help" in args:
        ogHelp(FUNCNAME, f"{FUNCNAME} int_ndisk str_repo path_image", 
                f"{FUNCNAME} 1 REPO /disk1")
        return

    # Error si no se reciben entre 3 y 5 parámetros.
    if not (3 <= len(args) <= 5):
        ogRaiseError(OG_ERR_FORMAT, " ".join(args))
        return

    # Comprobar que no está bloqueada ni la partición, ni la imagen.
    DISK = ogDiskToDev(args[0])
    if ogIsDiskLocked(args[0]):
        ogRaiseError(OG_ERR_LOCKED, f"{MSG_LOCKED} {args[0]}")
        return

    IMGTYPE = "dsk"  # Extensión genérica de imágenes de disco.
    IMGDIR = ogGetParentPath(args[1], args[2])
    if not IMGDIR:
        ogRaiseError(OG_ERR_NOTFOUND, f"{args[1]} {os.path.dirname(args[2])}")
        return

    IMGFILE = f"{IMGDIR}/{os.path.basename(args[2])}.{IMGTYPE}"
    if ogIsImageLocked(IMGFILE):
        ogRaiseError(OG_ERR_LOCKED, f"{MSG_IMAGE} {args[2]}, {args[3]}")
        return

    # No guardar imagen en el propio disco (disco no incluido en el camino del repositorio).
    if ogGetPath(args[1], "/").startswith(DISK):
        ogRaiseError(OG_ERR_IMAGE, f"{args[1]} = {DISK}")
        return

    # Generar la instruccion a ejecutar antes de aplicar los bloqueos.
    PROGRAM = ogCreateImageSyntax(DISK, IMGFILE)

    # Desmontar todos los sistemas de archivos del disco, bloquear disco e imagen.
    ogUnmountAll(args[0])
    ogLockDisk(args[0])
    ogLockImage(args[1], f"{args[2]}.{IMGTYPE}")

    # Crear Imagen.
    try:
        with open(IMGFILE + ".info", "w") as info_file:
            subprocess.run(PROGRAM, shell=True, check=True)
            info_file.write(f"{ogGetImageInfo(IMGFILE)}:{ogGetHostname()}")
    except subprocess.CalledProcessError:
        ogRaiseError(OG_ERR_IMAGE, f"{args[0]} {args[1]} {IMGFILE}")
        os.remove(IMGFILE)
        return ERRCODE

    # Desbloquear disco e imagen.
    ogUnlockDisk(args[0])
    ogUnlockImage(args[1], f"{args[2]}.{IMGTYPE}")

    return ERRCODE

def ogCreateImage(*args):
    FUNCNAME = ogCreateImage.__name__
    # Variables locales
    PART = ""
    PROGRAM = ""
    IMGDIR = ""
    IMGFILE = ""
    IMGTYPE = ""
    ERRCODE = ""

    # Si se solicita, mostrar ayuda.
    if "help" in args:
        ogHelp(FUNCNAME, f"{FUNCNAME} int_ndisk int_npart str_repo path_image", 
                f"{FUNCNAME} 1 1 REPO /aula1/win7")
        return

    # Error si no se reciben entre 4 y 6 parámetros.
    if not (4 <= len(args) <= 6):
        ogRaiseError(OG_ERR_FORMAT, " ".join(args))
        return

    # Comprobar que no está bloqueada ni la partición, ni la imagen.
    PART = ogDiskToDev(args[0], args[1])
    if ogIsLocked(args[0], args[1]):
        ogRaiseError(OG_ERR_LOCKED, f"{MSG_LOCKED} {args[0]}, {args[1]}")
        return

    IMGTYPE = "img"  # Extensión genérica de imágenes.
    IMGDIR = ogGetParentPath(args[2], args[3])
    if not IMGDIR:
        ogRaiseError(OG_ERR_NOTFOUND, f"{args[2]} {os.path.dirname(args[3])}")
        return

    IMGFILE = f"{IMGDIR}/{os.path.basename(args[3])}.{IMGTYPE}"
    if ogIsImageLocked(IMGFILE):
        ogRaiseError(OG_ERR_LOCKED, f"{MSG_IMAGE} {args[2]}, {args[3]}")
        return

    # Generar la instruccion a ejecutar antes de aplicar los bloqueos.
    PROGRAM = ogCreateImageSyntax(PART, IMGFILE, *args[4:])

    # Desmontar partición, bloquear partición e imagen.
    ogUnmount(args[0], args[1])
    ogLock(args[0], args[1])

    # Crear Imagen.
    try:
        with open(f"{IMGFILE}.info", "w") as info_file:
            subprocess.run(PROGRAM, shell=True, check=True)
            info_file.write(f"{ogGetImageInfo(IMGFILE)}:{ogGetHostname()}")
    except subprocess.CalledProcessError:
        ogRaiseError(OG_ERR_IMAGE, f"{args[0]} {args[1]} {IMGFILE}")
        os.remove(IMGFILE)
        return ERRCODE

    # Desbloquear partición e imagen.
    ogUnlock(args[0], args[1])
    ogUnlockImage(args[2], f"{args[3]}.{IMGTYPE}")

    return ERRCODE

def ogCreateMbrImage(*args):
    FUNCNAME = ogCreateMbrImage.__name__
    # Variables locales
    DISK = ""
    IMGDIR = ""
    IMGFILE = ""

    # Si se solicita, mostrar ayuda.
    if "help" in args:
        ogHelp(FUNCNAME, f"{FUNCNAME} int_ndisk str_repo path_image", 
                f"{FUNCNAME} 1 REPO /aula1/mbr")
        return

    # Error si no se reciben 3 parámetros.
    if len(args) != 3:
        ogRaiseError(OG_ERR_FORMAT)
        return

    DISK = ogDiskToDev(args[0])
    if not DISK:
        ogRaiseError(OG_ERR_NOTFOUND, args[0])
        return

    IMGDIR = ogGetParentPath(args[1], args[2])
    if not IMGDIR:
        ogRaiseError(OG_ERR_NOTFOUND, f"{args[1]} {os.path.dirname(args[2])}")
        return

    IMGFILE = f"{IMGDIR}/{os.path.basename(args[2])}.mbr"

    # Crear imagen del MBR.
    try:
        subprocess.run(["dd", f"if={DISK}", f"of={IMGFILE}", "bs=512", "count=1"], check=True)
    except subprocess.CalledProcessError:
        ogRaiseError(OG_ERR_IMAGE, f"{args[0]} {IMGFILE}")
        return

    return

def ogCreateBootLoaderImage(*args):
    FUNCNAME = ogCreateBootLoaderImage.__name__
    # Variables locales
    DISK = ""
    IMGDIR = ""
    IMGFILE = ""

    # Si se solicita, mostrar ayuda.
    if "help" in args:
        ogHelp(FUNCNAME, f"{FUNCNAME} int_ndisk str_repo path_image", 
                f"{FUNCNAME} 1 REPO /aula1/mbr")
        return

    # Error si no se reciben 3 parámetros.
    if len(args) != 3:
        ogRaiseError(OG_ERR_FORMAT)
        return

    DISK = ogDiskToDev(args[0])
    if not DISK:
        ogRaiseError(OG_ERR_NOTFOUND, args[0])
        return

    IMGDIR = ogGetParentPath(args[1], args[2])
    if not IMGDIR:
        ogRaiseError(OG_ERR_NOTFOUND, f"{args[1]} {os.path.dirname(args[2])}")
        return

    IMGFILE = f"{IMGDIR}/{os.path.basename(args[2])}.mbr"

    # Crear imagen del Boot Loader dentro del MBR.
    try:
        subprocess.run(["dd", f"if={DISK}", f"of={IMGFILE}", "bs=446", "count=1"], check=True)
    except subprocess.CalledProcessError:
        ogRaiseError(OG_ERR_IMAGE, f"{args[0]} {IMGFILE}")
        return

    return

def ogGetSizeParameters(*args):
    FUNCNAME = ogGetSizeParameters.__name__
    # Variables locales
    REPO = ""
    MNTDIR = ""
    SIZEDATA = ""
    KERNELVERSION = ""
    SIZEREQUIRED = ""
    FACTORGZIP = ""
    FACTORLZOP = ""
    FACTORSYNC = ""
    SIZEFREE = ""
    IMGTYPE = ""
    IMGDIR = ""
    IMGFILE = ""
    IMGEXT = ""
    IMGSIZE = ""
    ISENOUGHSPACE = ""

    # Si se solicita, mostrar ayuda.
    if "help" in args:
        ogHelp(FUNCNAME, f"{FUNCNAME} num_disk num_part str_repo path_imgname [monolit|sync|diff]",
                f"if {FUNCNAME} 1 2 REPO Windows10 sync ; then ...; fi",
                f"if {FUNCNAME} 1 6 Ubuntu16 CACHE ; then ...; fi")
        return

    # Error si no se reciben 1 o 2 parámetros.
    if len(args) < 4:
        return ogRaiseError(OG_ERR_FORMAT, f"{MSG_FORMAT}: {PROG} ndisco nparticion REPO|CACHE imgname [monolit|sync|diff]")

    # Recogemos parametros
    REPO = args[2].upper()
    IMGTYPE = f"_{args[4].upper()}_"

    MNTDIR = ogMount(args[0], args[1])
    if not MNTDIR:
        return ogRaiseError(OG_ERR_PARTITION, f"{args[0]} {args[1]}")

    # Datos contenidos en la particion o en la lista de archivos de contiene la diferencial.
    if IMGTYPE == "_DIFF_":
        if not os.path.isfile("/tmp/ogimg.info"):
            return ogRaiseError(OG_ERR_NOTFOUND, "/tmp/ogimg.info")
        os.chdir(MNTDIR)
        with open("/tmp/ogimg.info", "r") as info_file:
            lines = info_file.readlines()
            lines = [line.strip() for line in lines if line.strip() != "/"]
            SIZEDATA = sum([os.path.getsize(line) for line in lines])
        os.chdir("/")
    else:
        df_output = subprocess.run(["df", "-k"], capture_output=True, text=True).stdout
        df_lines = df_output.split("\n")
        for line in df_lines:
            if line.endswith(MNTDIR):
                SIZEDATA = line.split()[2]
                break

    # Aplicar factor de compresion
    if IMGTYPE == "_SYNC_" or IMGTYPE == "_DIFF_":
        # Sistema de fichero de la imagen según kernel, menor que 3.7 EXT4. comparamos revision
        KERNELVERSION = os.uname().release
        KERNELVERSION = float(KERNELVERSION.split("-")[0])
        IMGFS = "EXT4" if KERNELVERSION < 3.07 else "BTRFS"
        FACTORSYNC = 130
        # Si IMGFS="BTRFS" la compresion es mayor.
        if IMGFS == "BTRFS":
            FACTORSYNC -= 20

        SIZEREQUIRED = SIZEDATA * FACTORSYNC // 100
        # El tamaño mínimo del sistema de ficheros btrfs es 250M, ponemos 300
        if SIZEREQUIRED < 300000:
            SIZEREQUIRED = 300000
    else:
        FACTORGZIP = 55 / 100
        FACTORLZOP = 65 / 100
        SIZEREQUIRED = SIZEDATA * FACTORLZOP

    # Comprobar espacio libre en el contenedor.
    if REPO == "CACHE":
        SIZEFREE = ogGetFreeSize(ogFindCache())
    elif REPO == "REPO":
        df_output = subprocess.run(["df", "-k"], capture_output=True, text=True).stdout
        df_lines = df_output.split("\n")
        for line in df_lines:
            if line.endswith(OGIMG):
                SIZEFREE = line.split()[3]
                break

    # Comprobamos si existe una imagen con el mismo nombre en $REPO
    # En sincronizadas restamos tamaño de la imagen y en monoloticas de la .ant
    if IMGTYPE == "_DIFF_":
        IMGEXT = "img.diff"
    elif IMGTYPE == "_SYNC_":
        IMGEXT = "img"
    else:
        IMGEXT = "img.ant"

    IMGDIR = ogGetParentPath(REPO, f"/{args[3]}")
    IMGFILE = ogGetPath(f"{IMGDIR}/{os.path.basename(f'/{args[3]}')}.{IMGEXT}")
    if IMGFILE == "":
        IMGSIZE = 0
    else:
        IMGSIZE = os.path.getsize(IMGFILE)

    SIZEFREE += IMGSIZE

    ISENOUGHSPACE = "TRUE" if SIZEREQUIRED < SIZEFREE else "FALSE"

    return f"{SIZEDATA} {SIZEREQUIRED} {SIZEFREE} {ISENOUGHSPACE}"

def ogIsImageLocked(*args):
    FUNCNAME = ogIsImageLocked.__name__
    # Si se solicita, mostrar ayuda.
    if "help" in args:
        ogHelp(FUNCNAME, f"{FUNCNAME} [str_repo] path_image",
                f"if {FUNCNAME} /opt/opengnsys/images/aula1/win7.img; then ...; fi",
                f"if {FUNCNAME} REPO /aula1/win7.img; then ...; fi")
        return

    # Error si no se reciben 1 o 2 parámetros.
    if len(args) < 1 or len(args) > 2:
        return 1

    # Comprobar si existe el fichero de bloqueo.
    if ogGetPath(*args) + ".lock":
        return True
    else:
        return False
    
def ogLockImage(*args):
    FUNCNAME = ogLockImage.__name__
    # Si se solicita, mostrar ayuda.
    if "help" in args:
        ogHelp(FUNCNAME, f"{FUNCNAME} [str_repo] path_image",
                f"if {FUNCNAME} /opt/opengnsys/images/aula1/win7.img; then ...; fi",
                f"if {FUNCNAME} REPO /aula1/win7.img; then ...; fi")
        return

    # Error si no se reciben 1 o 2 parámetros.
    if len(args) < 1 or len(args) > 2:
        return 1

    # Comprobar que existe directorio de imagen
    IMGDIR = ogGetParentPath(*args)
    if not IMGDIR:
        return

    # Crear fichero de bloqueo.
    try:
        with open(f"{IMGDIR}/{os.path.basename(args[-1])}.lock", "w") as lock_file:
            pass
    except IOError:
        ogRaiseError(OG_ERR_NOTWRITE, " ".join(args))
        return

    return

def ogRestoreDiskImage(*args):
    FUNCNAME = ogRestoreDiskImage.__name__
    # Variables locales
    DISK = ""
    DISKSIZE = ""
    IMGFILE = ""
    IMGTYPE = ""
    IMGSIZE = ""
    PROGRAM = ""
    ERRCODE = ""

    # Si se solicita, mostrar ayuda.
    if "help" in args:
        ogHelp(FUNCNAME, f"{FUNCNAME} str_repo path_image int_ndisk",
                f"{FUNCNAME} REPO /aula1/win7 1")
        return

    # Error si no se reciben 3 parámetros.
    if len(args) != 3:
        ogRaiseError(OG_ERR_FORMAT)
        return

    # Procesar parámetros.
    DISK = ogDiskToDev(args[2])
    if not DISK:
        return ogRaiseError(OG_ERR_NOTFOUND, f" {args[2]} {args[3]}")

    IMGTYPE = "dsk"
    IMGFILE = ogGetPath(args[0], f"{args[1]}.{IMGTYPE}")
    if not os.path.isfile(IMGFILE):
        return ogRaiseError(OG_ERR_NOTFOUND, f" {args[2]} {args[3]}")

    # comprobamos consistencia de la imagen
    if not ogGetImageInfo(IMGFILE):
        return ogRaiseError(OG_ERR_IMAGE, f" {args[0]} {args[1]}")

    #/* (Comienzo comentario Doxygen)
    # Error si la imagen no cabe en la particion.
    #IMGSIZE=$(ogGetImageSize "$1" "$2") || return $(ogRaiseError $OG_ERR_IMAGE " $1 $2"; echo $?)
    #DISKSIZE=$(ogGetDiskSize $3)
    #if [ $IMGSIZE -gt $DISKSIZE ]; then
    #    ogRaiseError $OG_ERR_IMGSIZEPARTITION "$DISKSIZE < $IMGSIZE"
    #    return $?
    #fi
    #*/ (Fin comentario Doxygen)

    # Comprobar el bloqueo de la imagen y de la partición.
    if ogIsImageLocked(IMGFILE):
        return ogRaiseError(OG_ERR_LOCKED, f"{MSG_IMAGE} {args[0]}, {args[1]}.{IMGTYPE}")
    if ogIsDiskLocked(args[2]):
        return ogRaiseError(OG_ERR_LOCKED, f"{MSG_DISK} {args[2]}")

    # Solicitamos la generación de la instruccion a ejecutar
    PROGRAM = ogRestoreImageSyntax(IMGFILE, DISK)

    # Bloquear el disco
    ogLockDisk(args[2])
    try:
        # Ejecutar restauración según el tipo de imagen.
        subprocess.run(PROGRAM, shell=True, check=True)
    except subprocess.CalledProcessError:
        ogRaiseError(OG_ERR_IMAGE, f"{IMGFILE}, {args[2]}, {args[3]}")
        ERRCODE = 1

    ogUnlockDisk(args[2], args[3])
    return ERRCODE

def ogRestoreImage(*args):
    FUNCNAME = ogRestoreImage.__name
    # Variables locales
    PART = ""
    PARTSIZE = ""
    IMGFILE = ""
    IMGTYPE = ""
    IMGSIZE = ""
    PROGRAM = ""
    ERRCODE = ""

    # Si se solicita, mostrar ayuda.
    if "help" in args:
        ogHelp(FUNCNAME, f"{FUNCNAME} str_repo path_image int_ndisk int_npart",
                f"{FUNCNAME} REPO /aula1/win7 1 1")
        return

    # Error si no se reciben 4 parámetros.
    if len(args) != 4:
        ogRaiseError(OG_ERR_FORMAT)
        return

    # Procesar parámetros.
    PART = ogDiskToDev(args[2], args[3])
    if not PART:
        return ogRaiseError(OG_ERR_NOTFOUND, f" {args[2]} {args[3]}")

    IMGTYPE = "img"
    IMGFILE = ogGetPath(args[0], f"{args[1]}.{IMGTYPE}")
    if not os.path.isfile(IMGFILE):
        return ogRaiseError(OG_ERR_NOTFOUND, f" {args[2]} {args[3]}")

    # comprobamos consistencia de la imagen
    if not ogGetImageInfo(IMGFILE):
        return ogRaiseError(OG_ERR_IMAGE, f" {args[0]} {args[1]}")

    # Error si la imagen no cabe en la particion.
    IMGSIZE = ogGetImageSize(args[0], args[1])
    PARTSIZE = ogGetPartitionSize(args[2], args[3])
    if IMGSIZE > PARTSIZE:
        ogRaiseError(OG_ERR_IMGSIZEPARTITION, f" {PARTSIZE} < {IMGSIZE}")
        return

    # Comprobar el bloqueo de la imagen y de la partición.
    if ogIsImageLocked(IMGFILE):
        return ogRaiseError(OG_ERR_LOCKED, f"{MSG_IMAGE} {args[0]}, {args[1]}.{IMGTYPE}")
    if ogIsLocked(args[2], args[3]):
        return ogRaiseError(OG_ERR_LOCKED, f"{MSG_PARTITION} {args[2]}, {args[3]}")

    # Solicitamos la generación de la instruccion a ejecutar
    PROGRAM = ogRestoreImageSyntax(IMGFILE, PART)

    # Desmontar y bloquear partición.
    ogUnmount(args[2], args[3])
    ogLock(args[2], args[3])
    try:
        # Ejecutar restauración según el tipo de imagen.
        subprocess.run(PROGRAM, shell=True, check=True)
    except subprocess.CalledProcessError:
        ogRaiseError(OG_ERR_IMAGE, f"{IMGFILE}, {args[2]}, {args[3]}")
        ERRCODE = 1

    ogUnlock(args[2], args[3])
    return ERRCODE

def ogRestoreMbrImage(*args):
    FUNCNAME = ogRestoreMbrImage.__name__
    # Variables locales
    DISK = ""
    IMGFILE = ""

    # Si se solicita, mostrar ayuda.
    if "help" in args:
        ogHelp(FUNCNAME, f"{FUNCNAME} str_repo path_image int_ndisk",
                f"{FUNCNAME} REPO /aula1/mbr 1")
        return

    # Error si no se reciben 3 parámetros.
    if len(args) != 3:
        ogRaiseError(OG_ERR_FORMAT)
        return

    # Procesar parámetros.
    DISK = ogDiskToDev(args[2])
    if not DISK:
        return ogRaiseError(OG_ERR_NOTFOUND, args[2])
    
    IMGFILE = ogGetPath(args[0], f"{args[1]}.mbr")
    if not os.path.isfile(IMGFILE):
        return ogRaiseError(OG_ERR_NOTFOUND, IMGFILE)

    # Restaurar imagen del MBR.
    try:
        subprocess.run(["dd", f"if={IMGFILE}", f"of={DISK}", "bs=512", "count=1"], check=True)
    except subprocess.CalledProcessError:
        ogRaiseError(OG_ERR_IMAGE, f"{args[0]} {IMGFILE}")
        return

    return

def ogRestoreBootLoaderImage(*args):
    FUNCNAME = ogRestoreBootLoaderImage.__name__
    # Variables locales
    DISK = ""
    IMGFILE = ""

    # Si se solicita, mostrar ayuda.
    if "help" in args:
        ogHelp(FUNCNAME, f"{FUNCNAME} str_repo path_image int_ndisk",
                f"{FUNCNAME} REPO /aula1/mbr 1")
        return

    # Error si no se reciben 3 parámetros.
    if len(args) != 3:
        ogRaiseError(OG_ERR_FORMAT)
        return

    # Procesar parámetros.
    DISK = ogDiskToDev(args[2])
    if not DISK:
        return ogRaiseError(OG_ERR_NOTFOUND, args[2])
    
    IMGFILE = ogGetPath(args[0], f"{args[1]}.mbr")
    if not os.path.isfile(IMGFILE):
        return ogRaiseError(OG_ERR_NOTFOUND, IMGFILE)

    # Restaurar imagen del MBR.
    try:
        subprocess.run(["dd", f"if={IMGFILE}", f"of={DISK}", "bs=446", "count=1"], check=True)
    except subprocess.CalledProcessError:
        ogRaiseError(OG_ERR_IMAGE, f"{args[0]} {IMGFILE}")
        return

    return

def ogUnlockImage(*args):
    FUNCNAME = ogUnlockImage.__name__
    # Si se solicita, mostrar ayuda.
    if "help" in args:
        ogHelp(FUNCNAME, f"{FUNCNAME} [str_repo] path_image",
                f"if {FUNCNAME} /opt/opengnsys/images/aula1/win7.img; then ...; fi",
                f"if {FUNCNAME} REPO /aula1/win7.img; then ...; fi")
        return

    # Error si no se reciben 1 o 2 parámetros.
    if len(args) < 1 or len(args) > 2:
        return 1

    # Borrar fichero de bloqueo para la imagen.
    lock_file = f"{ogGetPath(*args)}.lock"
    os.remove(lock_file)

    return

def ogGetImageInfo(*args):
    FUNCNAME = ogGetImageInfo.__name__
    # Si se solicita, mostrar ayuda.
    if "help" in args:
        ogHelp(FUNCNAME, f"{FUNCNAME} path_filename",
                f"{FUNCNAME} /opt/opengnsys/images/prueba.img  ==>  PARTCLONE:LZOP:NTFS:5642158")
        return

    # Error si no se recibe 1 parámetro.
    if len(args) != 1:
        ogRaiseError(OG_ERR_FORMAT)
        return

    # Comprobando que el parámetro uno es un archivo.
    if not os.path.isfile(args[0]):
        ogRaiseError(OG_ERR_NOTFOUND, args[0])
        return

    IMGDETECT = "FALSE"
    IMGFILE = args[0]
    FILEHEAD = f"/tmp/{os.path.basename(IMGFILE)}.infohead"
    COMPRESSOR = subprocess.run(["file", IMGFILE], capture_output=True, text=True).stdout.split()[1]
    if not ogCheckStringInGroup(COMPRESSOR, "gzip lzop"):
        ogRaiseError(OG_ERR_IMAGE, f"Image format is not valid {IMGFILE}")
        return

    try:
        with open(FILEHEAD, "w") as filehead:
            subprocess.run([COMPRESSOR, "-dc", IMGFILE], stdout=filehead, stderr=subprocess.DEVNULL)
    except subprocess.CalledProcessError:
        ogRaiseError(OG_ERR_IMAGE, f"Image format is not valid {IMGFILE}")
        return

    # Buscando Primera opción.
    if IMGDETECT == "FALSE":
        PARTCLONEINFO = subprocess.run(["partclone.info", FILEHEAD], capture_output=True, text=True).stdout
        if "size" in PARTCLONEINFO:
            TOOLS = "PARTCLONE"
            FS = PARTCLONEINFO.split(": ")[7].upper()
            if FS in ["HFS", "HFSPLUS", "FAT32"]:
                FSPLUS = PARTCLONEINFO.split(": ")[8].upper()
                SIZEFACTOR = 1000000 if "GB" in PARTCLONEINFO else 1024
                if FSPLUS == "PLUS":
                    FS = FS + FSPLUS
                    SIZE = int(PARTCLONEINFO.split(": ")[16]) * SIZEFACTOR
                else:
                    SIZE = int(PARTCLONEINFO.split(": ")[15]) * SIZEFACTOR
            else:
                SIZEFACTOR = 1000000 if "GB" in PARTCLONEINFO else 1024
                SIZE = int(PARTCLONEINFO.split(": ")[10]) * SIZEFACTOR
            IMGDETECT = "TRUE"

    # Buscando segunda opción.
    if IMGDETECT == "FALSE" and not os.path.exists("/dev/loop2"):
        if "ntfsclone-image" in open(FILEHEAD).read():
            NTFSCLONEINFO = subprocess.run(["ntfsclone", "--restore", "--overwrite", "/dev/loop2", "-"], input=open(FILEHEAD).read(), capture_output=True, text=True).stdout
            if "ntfsclone" in NTFSCLONEINFO:
                TOOLS = "NTFSCLONE"
                SIZE = int(NTFSCLONEINFO.split("(")[1].split(".")[0]) // 1000
                FS = "NTFS"
                IMGDETECT = "TRUE"

    # Buscando Tercer opción.
    if IMGDETECT == "FALSE":
        PARTIMAGEINFO = subprocess.run(["partimage", "-B", "gui=no", "imginfo", FILEHEAD], capture_output=True, text=True).stdout
        if "Partition" in PARTIMAGEINFO:
            TOOLS = "PARTIMAGE"
            FS = PARTIMAGEINFO.split()[16].upper()
            SIZE = int(PARTIMAGEINFO.split()[35]) * 1024 * 1024
            IMGDETECT = "TRUE"
        if "boot sector" in subprocess.run(["file", FILEHEAD], capture_output=True, text=True).stdout:
            TOOLS = "partclone.dd"
            FS = ""
            SIZE = ""
            IMGDETECT = "TRUE"

    # Comprobamos valores
    if not TOOLS or not COMPRESSOR or IMGDETECT == "FALSE":
        ogRaiseError(OG_ERR_IMAGE, f"Image format is not valid {IMGFILE}")
        return
    else:
        COMPRESSOR = COMPRESSOR.upper()
        return f"{TOOLS}:{COMPRESSOR}:{FS}:{SIZE}"
    
def ogGetImageProgram(*args):
    FUNCNAME = ogGetImageProgram.__name__
    # Si se solicita, mostrar ayuda.
    if "help" in args:
        return ogHelp(FUNCNAME, f"{FUNCNAME} str_repo path_image",
                        f"{FUNCNAME} REPO prueba  ==>  PARTCLONE")

    # Error si no se reciben 2 parámetros.
    if len(args) != 2:
        return ogRaiseError(OG_ERR_FORMAT)

    IMGFILE = ogGetPath(args[0], f"{args[1]}.img")
    if not os.path.isfile(IMGFILE):
        return ogRaiseError(OG_ERR_NOTFOUND, IMGFILE)

    return ogGetImageInfo(IMGFILE).split(":")[0]

def ogGetImageCompressor(*args):
    FUNCNAME = ogGetImageCompressor.__name__
    # Si se solicita, mostrar ayuda.
    if "help" in args:
        ogHelp(FUNCNAME, f"{FUNCNAME} str_repo path_image",
                f"{FUNCNAME} REPO prueba  ==>  LZOP")
        return

    # Error si no se reciben 2 parámetros.
    if len(args) != 2:
        ogRaiseError(OG_ERR_FORMAT)
        return

    IMGFILE = ogGetPath(args[0], f"{args[1]}.img")
    if not os.path.isfile(IMGFILE):
        ogRaiseError(OG_ERR_NOTFOUND, IMGFILE)
        return

    return ogGetImageInfo(IMGFILE).split(":")[1]

def ogGetImageType(*args):
    FUNCNAME = ogGetImageType.__name__
    # Si se solicita, mostrar ayuda.
    if "help" in args:
        ogHelp(FUNCNAME, f"{FUNCNAME} str_repo path_image",
                f"{FUNCNAME} REPO prueba  ==>  NTFS")
        return

    # Error si no se reciben 2 parámetros.
    if len(args) != 2:
        ogRaiseError(OG_ERR_FORMAT)
        return

    IMGFILE = ogGetPath(args[0], f"{args[1]}.img")
    if not os.path.isfile(IMGFILE):
        ogRaiseError(OG_ERR_NOTFOUND, IMGFILE)
        return

    return ogGetImageInfo(IMGFILE).split(":")[2]

def ogGetImageSize(*args):
    FUNCNAME = ogGetImageSize.__name__
    # Si se solicita, mostrar ayuda.
    if "help" in args:
        ogHelp(FUNCNAME, f"{FUNCNAME} str_repo path_image",
                f"{FUNCNAME} REPO prueba  ==>  5642158")
        return

    # Error si no se reciben 2 parámetros.
    if len(args) != 2:
        ogRaiseError(OG_ERR_FORMAT)
        return

    IMGFILE = ogGetPath(args[0], f"{args[1]}.img")
    if not os.path.isfile(IMGFILE):
        ogRaiseError(OG_ERR_NOTFOUND, IMGFILE)
        return

    return ogGetImageInfo(IMGFILE).split(":")[3]

def ogCreateGptImage(*args):
    FUNCNAME = ogCreateGptImage.__name__
    # Variables locales
    DISK = ""
    IMGDIR = ""
    IMGFILE = ""

    # Si se solicita, mostrar ayuda.
    if "help" in args:
        ogHelp(FUNCNAME, f"{FUNCNAME} int_ndisk path_dir str_image",
                f"{FUNCNAME} 1 REPO /aula1/gpt")
        return

    # Error si no se reciben 3 parámetros.
    if len(args) != 3:
        ogRaiseError(OG_ERR_FORMAT)
        return

    DISK = ogDiskToDev(args[0])
    if not DISK:
        return ogRaiseError(OG_ERR_NOTFOUND, args[0])

    IMGDIR = ogGetParentPath(args[1], args[2])
    if not IMGDIR:
        return ogRaiseError(OG_ERR_NOTFOUND, f"{args[1]} {os.path.dirname(args[2])}")

    IMGFILE = f"{IMGDIR}/{os.path.basename(args[2])}.gpt"

    # Crear imagen de la tabla GPT.
    try:
        subprocess.run(["sgdisk", f"-b={IMGFILE}", DISK], check=True)
    except subprocess.CalledProcessError:
        return ogRaiseError(OG_ERR_IMAGE, f"{args[0]} {IMGFILE}")

    return

def ogRestoreGptImage(*args):
    FUNCNAME = ogRestoreGptImage.__name__
    # Variables locales
    DISK = ""
    IMGFILE = ""

    # Si se solicita, mostrar ayuda.
    if "help" in args:
        ogHelp(FUNCNAME, f"{FUNCNAME} path_dir str_image int_ndisk",
                f"{FUNCNAME} REPO /aula1/gpt 1")
        return

    # Error si no se reciben 3 parámetros.
    if len(args) != 3:
        ogRaiseError(OG_ERR_FORMAT)
        return

    DISK = ogDiskToDev(args[2])
    if not DISK:
        return ogRaiseError(OG_ERR_NOTFOUND, args[2])

    IMGFILE = ogGetPath(args[0], f"{args[1]}.gpt")
    if not os.path.isfile(IMGFILE):
        return ogRaiseError(OG_ERR_NOTFOUND, IMGFILE)

    # Restaurar tabla GPT del disco.
    try:
        subprocess.run(["sgdisk", f"-l={IMGFILE}", DISK], check=True)
    except subprocess.CalledProcessError:
        return ogRaiseError(OG_ERR_IMAGE, f"{args[0]} {IMGFILE}")

    return