#!/usr/bin/python3
#/**
#         updateCache
#@brief   Actualiza la cache del cliente con imagen o fichero iso.
#@param 1 REPO          Origen del fichero. -accesible por nfs-samba-
#@param 2 str_fichero   nombre del fichero a actualizar.
#@param 3 str_protoco.  TORRENT | MULTICAST    | UNICAST.
#@param 4 str_opcionesprotocolo
#@param 4 str_opcionesupdatecache
#@ejemplo: updateCache.py REPO imgname.img UNICAST 8042:42
#@return  
#@exception OG_ERR_FORMAT       formato incorrecto.
#@exception OG_ERR_NOTCACHE     No existe cache -15-
#@exception $OG_ERR_CACHESIZE   Tamaño de la paticion menor al archivo a descargar -16-
#@exception $OG_ERR_MCASTRECEIVERFILE  Error en la recepción Multicast de un fichero -57- 
#@exception $OG_ERR_PROTOCOLJOINMASTER Error en la conexión de una sesión Unicast|Multicast con el Master -60- 
#@note   
#@todo: 
#*/ ##

import os.path
import sys
import re
import time
import subprocess
import shutil
import glob
import random

import ogGlobals
import SystemLib
import StringLib
import NetLib
import CacheLib
import FileLib
import ProtocolLib
import FileSystemLib

prog = os.path.basename (sys.argv[0])
print (f'argv ({sys.argv}) len ({len (sys.argv)})')
if len (sys.argv) < 3:
    SystemLib.ogRaiseError ('session', ogGlobals.OG_ERR_FORMAT, f'{ogGlobals.lang.MSG_FORMAT}: {prog} str_REPO _str_Relative_Path_OGIMG_with_/ PROTOCOLO OPCIONES_PROTOCOLO OPCIONES_UPDATECACHE')
    sys.exit (1)
_, repositorio, path, protocolo, *other = sys.argv
optprotocolo = other[0] if len (other) > 0 else ''
cacheopts    = other[1] if len (other) > 1 else ''

if 'RSYNC' == protocolo:
    raise Exception ('synchronised images are no longer supported')

#Carga del configurador del engine
## (ogGlobals se encarga)

# Clear temporary file used as log track by httpdlog
# Limpia los ficheros temporales usados como log de seguimiento para httpdlog
open (ogGlobals.OGLOGCOMMAND, 'w').close()
if SystemLib.ogGetCaller() not in ['deployImage', 'restoreBaseImage', 'restoreDiffImage']:
    open (ogGlobals.OGLOGSESSION, 'w').close()
    SystemLib.ogEcho (['log', 'session'], None, f'[1] {ogGlobals.lang.MSG_SCRIPTS_START} {prog} {sys.argv}')

# Si MCASTWAIT menos que tiempo de espera del servidor lo aumento
MCASTWAIT = ogGlobals.MCASTWAIT
if ':' in optprotocolo:
    port, wait, *other = optprotocolo.split (':')
else:
    port, wait = ('', '')
if protocolo.startswith ('MULTICAST') and re.match (r'^-?\d+$', wait):
    if int (MCASTWAIT or 0) < int (wait):
        MCASTWAIT = int (wait) + 5
# Unidad organizativa.
## (no longer supported)
#print (f'repositorio ({repositorio}) path ({path}) protocolo ({protocolo}) optprotocolo ({optprotocolo}) cacheopts ({cacheopts}) MCASTWAIT ({MCASTWAIT})')

# Si es una ip y es distinta a la del recurso samba cambiamos de REPO.
if StringLib.ogCheckIpAddress (repositorio) or 'REPO' == repositorio:
    if not NetLib.ogChangeRepo (repositorio):
        SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTFOUND, repositorio)
        sys.exit (1)
    repositorio = 'REPO'
repoip = NetLib.ogGetRepoIp()
SystemLib.ogEcho (['log', 'session'], None, f'{repositorio} {repoip} {protocolo} {optprotocolo}')

# Si el repositorio local CACHE no existe error 15.
if not CacheLib.ogFindCache():
    SystemLib.ogRaiseError ('session', ogGlobals.OG_ERR_NOTCACHE, 'CACHE')
    sys.exit (1)

# comprobar si la imagen existe (.img, .img.diff o directorio)
repofile = FileLib.ogGetPath ('REPO', f'/{path}')
if not os.path.exists (repofile):
    SystemLib.ogRaiseError ('session', ogGlobals.OG_ERR_NOTFOUND, f'REPO /{path}')
    sys.exit (1)

SystemLib.ogEcho (['log', 'session'], None, ogGlobals.lang.MSG_SCRIPTS_UPDATECACHE_DOUPDATE)
# Distingo si es monolitica o sincronizable
f = subprocess.run (['file', repofile], capture_output=True, text=True).stdout.lower()
if ' btrfs filesystem ' in f or ' ext4 filesystem ' in f or ' directory' in f:
    raise Exception ('synchronised images are no longer supported')
rc = ProtocolLib.ogUpdateCacheIsNecesary (repositorio, path, protocolo)
# si rc=True: actualizamos; si rc=False: no actualizamos (exit 0); si rc=None: exit error
if   rc == True:  pass                                     ## es necesario actualizar
elif rc == False: sys.exit (0)                             ## no es necesario actualizar
elif rc == None:  sys.exit (ogGlobals.OG_ERR_UPDATECACHE)  ## hubo errores

SystemLib.ogEcho (['log', 'session'], None, ogGlobals.lang.MSG_SCRIPTS_UPDATECACHE_CHECKSIZECACHE)
cachesize = CacheLib.ogGetCacheSize()
cache_disk, cache_par = CacheLib.ogFindCache().split()
cachesizefree = FileSystemLib.ogGetFreeSize (cache_disk, cache_par)
path_repo = FileLib.ogGetPath ('REPO', path)
filesize = int (subprocess.run (['ls', '-sk', path_repo], capture_output=True, text=True).stdout.split()[0])
realfilesize = subprocess.run (['stat', '--format', '%s', repofile], capture_output=True, text=True).stdout
realfilesize = int (int (realfilesize) / 1024)

# La sincronizada, si existe la imagen en cache el espacio necesario
#	 es la nueva menos lo que ocupa la que ya hay.
sizerequired = filesize

#ERROR CACHESIZE 16 (tamanyo de la CACHE insuficiente)
if sizerequired >= cachesize:
    SystemLib.ogEcho (['log', 'session'], None, f'{ogGlobals.lang.MSG_WARNING}: {ogGlobals.lang.MSG_ERR_CACHESIZE}:  {path} = {sizerequired} > CACHE = {cachesize}')
    SystemLib.ogRaiseError ('session', ogGlobals.OG_ERR_CACHESIZE, 'CACHE')
    sys.exit (1)


#ERROR CACHESIZE 16 (Espacio libre en CACHE insuficiente)
if sizerequired >= cachesizefree:
    SystemLib.ogEcho (['log', 'session'], None, f'{ogGlobals.lang.MSG_SCRIPTS_UPDATECACHE_IFNOTCACHEDO}: ACTIONCACHEFULL={ogGlobals.ACTIONCACHEFULL}')
    if 'NONE' == ogGlobals.ACTIONCACHEFULL:
        SystemLib.ogEcho (['log', 'session'], None, f'{ogGlobals.lang.MSG_WARNING}: {ogGlobals.lang.MSG_ERR_CACHESIZE}:  {path} = {sizerequired} > FREE SPACE CACHE = {cachesizefree}')
        SystemLib.ogRaiseError ('session', ogGlobals.OG_ERR_CACHESIZE, 'CACHE FULL, NO SPACE FREE')
        sys.exit (1)
    elif 'FORMAT' == ogGlobals.ACTIONCACHEFULL:
        SystemLib.ogEcho (['log', 'session'], None, f'[51] {ogGlobals.lang.MSG_HELP_ogFormatCache}')
        CacheLib.ogUnmountCache()
        CacheLib.ogFormatCache()
        CacheLib.ogMountCache()
    elif 'DELETE' == ogGlobals.ACTIONCACHEFULL:
        SystemLib.ogEcho (['log', 'session'], None, f'[51] {ogGlobals.lang.MSG_HELP_ogDeleteTree} {ogGlobals.OGCAC}{ogGlobals.OGIMG}/*')
        for d in glob.glob (f'{ogGlobals.OGCAC}{ogGlobals.OGIMG}/*'):
            shutil.rmtree (d)
    else:
        SystemLib.ogEcho (['log', 'session'], None, f'{ogGlobals.lang.MSG_WARNING}: {ogGlobals.lang.MSG_ERR_CACHESIZE}:  {path} = {filesize} > CACHE = {cachesizefree}')
        SystemLib.ogRaiseError ('session', ogGlobals.OG_ERR_CACHESIZE, 'CACHE')
        sys.exit (1)

# Comprobamos que imagen cache igual a la del repo. Si sincronizada no podemos comprobar. 
rc = ProtocolLib.ogUpdateCacheIsNecesary (repositorio, path, protocolo)
# si rc=True: actualizamos; si rc=False: no actualizamos (exit 0); si rc=None: exit error
if   rc == True:  pass                                     ## es necesario actualizar
elif rc == False: sys.exit (0)                             ## no es necesario actualizar
elif rc == None:  sys.exit (ogGlobals.OG_ERR_UPDATECACHE)  ## hubo errores

CacheLib.ogMountCache()

## Si no existe, crear subdirectorio para el fichero en la cache.
imgdir = FileLib.ogGetParentPath ('CACHE', f'/{path}')
if not imgdir:
    SystemLib.ogEcho (['log', 'session'], None, f'[5] {ogGlobals.lang.MSG_HELP_ogMakeDir}  "{path} {os.path.dirname (path)}".')
    FileLib.ogMakeDir ('CACHE', os.path.dirname (f'/{path}'))
    imgdir = ogGetParentPath ('CACHE', f'/{path}')
    if not imgdir:
        sys.exit (1)

t0 = time.time()

if 'TORRENT' == protocolo:
    SystemLib.ogEcho (['log', 'session'], None, f'ogCopyFile {repositorio} {path}.torrent absolute {ogGlobals.OGCAC}/{ogGlobals.OGIMG}')
    mac_digits = NetLib.ogGetMacAddress().split (':')
    timewait = int ('0x' + mac_digits[4] + mac_digits[5], 16) * 120 / 65535
    if not SystemLib.ogExecAndLog ('command', FileLib.ogCopyFile, {'container':repositorio, 'file':f'{path}.torrent'}, {'file':imgdir}):
        sys.exit (1)
    p2pwait = random.randint (1, 121)
    SystemLib.ogEcho (['log', 'session'], None, f'   [ ] {ogGlobals.lang.MSG_SCRIPTS_TASK_SLEEP} :  {p2pwait} seconds')
    time.sleep (p2pwait)
    SystemLib.ogEcho (['log', 'session'], None, f'   [  ] {ogGlobals.lang.MSG_SCRIPTS_TASK_START}:  ogTorrentStart CACHE {path}.torrent {optprotocolo}')
    SystemLib.ogExecAndLog ('command', ProtocolLib.ogTorrentStart, 'CACHE', f'{path}.torrent', optprotocolo)
    resumeupdatecache   = subprocess.run (['grep', '--max-count', '1', '--before-context', '1', 'Download', ogGlobals.OGLOGCOMMAND], capture_output=True, text=True).stdout
    resumeupdatecachebf = subprocess.run (['grep', '--max-count', '1',                          'Download', ogGlobals.OGLOGCOMMAND], capture_output=True, text=True).stdout
    if 'Download complete.' == resumeupdatecachebf:
        os.unlink (imgdir + path + '.torrent.bf')
elif 'MULTICAST' == protocolo:
    SystemLib.ogEcho (['log', 'session'], None, f'{ogGlobals.lang.MSG_SCRIPTS_UPDATECACHE_CHECKMCASTSESSION}: {repoip}:{port}')
    time.sleep (random.randint (1, 31))

    SystemLib.ogEcho (['log', 'session'], None, f'ogMcastRequest {path} {optprotocolo}')
    if not SystemLib.ogExecAndLog ('command', ProtocolLib.ogMcastRequest, path, optprotocolo):
        sys.exit (1)

    SystemLib.ogEcho (['log', 'session'], None, f'ogMcastReceiverFile {port} CACHE {path}')
    if not SystemLib.ogExecAndLog ('command', ProtocolLib.ogMcastReceiverFile, sess=port, container='CACHE', file=path):
        sys.exit (1)

    resumeupdatecache = subprocess.run (['grep', '--max-count', '1', '--before-context', '1', 'Transfer complete', f'{ogGlobals.OGLOGCOMMAND}.tmp'], capture_output=True, text=True).stdout

elif 'UNICAST' == protocolo:
    print (f'ogExecAndLog ("command", FileLib.ogCopyFile, {{"container":{repositorio}, "file":{path}}}, {{"file":{imgdir}}})')
    SystemLib.ogExecAndLog ('command', FileLib.ogCopyFile, {'container':repositorio, 'file':path}, {'file':imgdir})
    time.sleep (5)
    resumeupdatecache = subprocess.run (['grep', '--max-count', '1', '100%', f'{ogGlobals.OGLOGCOMMAND}.tmp'], capture_output=True, text=True).stdout

elif 'RSYNC' == protocolo:
    raise Exception ('synchronised images are no longer supported')

t = time.time() - t0
SystemLib.ogEcho (['log', 'session'], None, f'   [ ] {resumeupdatecache} ')
SystemLib.ogEcho (['log', 'session'], None, f'   [ ] {ogGlobals.lang.MSG_SCRIPTS_TIME_PARTIAL} updateCache {int (t/60)}m {int (t%60)}s')
SystemLib.ogEcho (['log', 'session'], None, f'   [ ] {ogGlobals.lang.MSG_SCRIPTS_TASK_START} {ogGlobals.lang.MSG_HELP_ogCalculateChecksum} ')
t0 = time.time()
# Si es imagen sincronizada siempre da distinto md5. No podemos comprobar 
rc = ProtocolLib.ogUpdateCacheIsNecesary (repositorio, path, protocolo)
if 'deployImage' != SystemLib.ogGetCaller():
    t = time.time() - t0
    SystemLib.ogEcho (['log', 'session'], None, f'   [ ] {ogGlobals.lang.MSG_SCRIPTS_TIME_PARTIAL} {ogGlobals.lang.MSG_HELP_ogCalculateChecksum} {int (t/60)}m {int (t%60)}s')

# si rc todavia es True: exit error; si rc=False: todo bien (exit 0); si rc=None: exit error
if   rc == True:  sys.exit (ogGlobals.OG_ERR_UPDATECACHE)
elif rc == False: sys.exit (0)
elif rc == None:  sys.exit (ogGlobals.OG_ERR_UPDATECACHE)
