Calc/app/mathjax_manager.py

120 lines
4.6 KiB
Python

"""
Sistema de gestión de MathJax local con descarga automática
"""
import os
import zipfile
import logging
from pathlib import Path
from urllib.request import urlretrieve
from urllib.error import URLError
class MathJaxManager:
"""Gestor de MathJax local con descarga automática"""
MATHJAX_VERSION = "3.2.2"
MATHJAX_URL = f"https://github.com/mathjax/MathJax/archive/{MATHJAX_VERSION}.zip"
LOCAL_DIR = Path(".mathjax")
def __init__(self):
self.logger = logging.getLogger(__name__)
self.mathjax_dir = self.LOCAL_DIR / f"MathJax-{self.MATHJAX_VERSION}"
self.es5_dir = self.mathjax_dir / "es5"
def get_mathjax_url(self) -> str:
"""
Obtiene la URL de MathJax (local si está disponible, CDN si no)
"""
if self.is_local_available():
# Construir URL local relativa para file://
local_url = f"file:///{self.es5_dir.absolute().as_posix()}/tex-svg.js"
self.logger.debug(f"Usando MathJax local: {local_url}")
return local_url
else:
# Descargar automáticamente si no existe
if self.download_mathjax():
local_url = f"file:///{self.es5_dir.absolute().as_posix()}/tex-svg.js"
self.logger.info(f"MathJax descargado y configurado localmente: {local_url}")
return local_url
else:
# Fallback a CDN
cdn_url = "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js"
self.logger.warning(f"Fallback a CDN: {cdn_url}")
return cdn_url
def is_local_available(self) -> bool:
"""Verifica si MathJax está disponible localmente"""
tex_svg_file = self.es5_dir / "tex-svg.js"
return tex_svg_file.exists() and tex_svg_file.is_file()
def download_mathjax(self) -> bool:
"""
Descarga MathJax automáticamente si no está disponible
Returns:
True si la descarga fue exitosa, False si falló
"""
try:
self.logger.info(f"Descargando MathJax {self.MATHJAX_VERSION}...")
# Crear directorio si no existe
self.LOCAL_DIR.mkdir(exist_ok=True)
# Archivo temporal para la descarga
zip_path = self.LOCAL_DIR / f"mathjax-{self.MATHJAX_VERSION}.zip"
# Descargar archivo ZIP
self.logger.debug(f"Descargando desde: {self.MATHJAX_URL}")
urlretrieve(self.MATHJAX_URL, zip_path)
# Extraer archivo
self.logger.debug(f"Extrayendo en: {self.LOCAL_DIR}")
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
zip_ref.extractall(self.LOCAL_DIR)
# Limpiar archivo ZIP
zip_path.unlink()
# Verificar que se extrajo correctamente
if self.is_local_available():
self.logger.info(f"✅ MathJax {self.MATHJAX_VERSION} descargado correctamente")
return True
else:
self.logger.error("❌ Error: MathJax no se extrajo correctamente")
return False
except URLError as e:
self.logger.error(f"❌ Error de red descargando MathJax: {e}")
return False
except zipfile.BadZipFile as e:
self.logger.error(f"❌ Error: archivo ZIP corrupto: {e}")
# Limpiar archivo corrupto
if zip_path.exists():
zip_path.unlink()
return False
except Exception as e:
self.logger.error(f"❌ Error inesperado descargando MathJax: {e}")
return False
def clean_local_mathjax(self):
"""
Limpia la instalación local de MathJax
(Para forzar redownload en la siguiente inicialización)
"""
try:
if self.LOCAL_DIR.exists():
import shutil
shutil.rmtree(self.LOCAL_DIR)
self.logger.info("🗑️ MathJax local limpiado")
except Exception as e:
self.logger.error(f"Error limpiando MathJax local: {e}")
def get_status_info(self) -> dict:
"""Obtiene información del estado de MathJax"""
return {
"local_available": self.is_local_available(),
"local_dir": str(self.LOCAL_DIR.absolute()),
"mathjax_dir": str(self.mathjax_dir.absolute()) if self.mathjax_dir.exists() else None,
"version": self.MATHJAX_VERSION,
"tex_svg_file": str(self.es5_dir / "tex-svg.js") if self.is_local_available() else None
}