SXXXXXXX_MarkdownConverter/markdownconverter/utils/error_handler.py
2025-12-03 10:07:33 +01:00

188 lines
6.1 KiB
Python

# markdownconverter/utils/error_handler.py
"""
Unified error handling for conversion operations.
Provides consistent error classification, logging, and user-friendly messages.
"""
import tkinter as tk
from tkinter import messagebox
from typing import Optional, Callable
from ..core.core import ConverterNotFoundError, TemplatePlaceholderError
from .logger import get_logger
log = get_logger(__name__)
class ConversionError(Exception):
"""Base exception for conversion errors."""
pass
class ErrorCategory:
"""Categories of conversion errors for better user feedback."""
FILE_NOT_FOUND = "file_not_found"
TEMPLATE_ERROR = "template_error"
CONVERTER_MISSING = "converter_missing"
CONVERSION_FAILED = "conversion_failed"
PERMISSION_ERROR = "permission_error"
TIMEOUT_ERROR = "timeout_error"
UNKNOWN = "unknown"
def classify_error(exception: Exception) -> str:
"""
Classify an exception into a specific error category.
Args:
exception: The exception to classify
Returns:
Error category string
"""
if isinstance(exception, FileNotFoundError):
return ErrorCategory.FILE_NOT_FOUND
elif isinstance(exception, TemplatePlaceholderError):
return ErrorCategory.TEMPLATE_ERROR
elif isinstance(exception, ConverterNotFoundError):
return ErrorCategory.CONVERTER_MISSING
elif isinstance(exception, PermissionError):
return ErrorCategory.PERMISSION_ERROR
elif "timeout" in str(exception).lower() or "timed out" in str(exception).lower():
return ErrorCategory.TIMEOUT_ERROR
elif isinstance(exception, (RuntimeError, ValueError)):
return ErrorCategory.CONVERSION_FAILED
else:
return ErrorCategory.UNKNOWN
def get_user_friendly_message(category: str, exception: Exception) -> tuple[str, str]:
"""
Get user-friendly title and message for an error category.
Args:
category: Error category from ErrorCategory
exception: The original exception
Returns:
Tuple of (title, message) for display
"""
messages = {
ErrorCategory.FILE_NOT_FOUND: (
"File Non Trovato",
f"Il file richiesto non è stato trovato:\n\n{str(exception)}\n\n"
"Verifica che il file esista e sia accessibile.",
),
ErrorCategory.TEMPLATE_ERROR: (
"Errore Template",
f"Il template presenta un problema:\n\n{str(exception)}\n\n"
"Verifica che il template contenga tutti i placeholder richiesti.",
),
ErrorCategory.CONVERTER_MISSING: (
"Convertitore Mancante",
f"{str(exception)}\n\n"
"Per la conversione PDF è necessario installare:\n"
"- Microsoft Word (consigliato), oppure\n"
"- LibreOffice (alternativa gratuita)",
),
ErrorCategory.CONVERSION_FAILED: (
"Conversione Fallita",
f"La conversione non è riuscita:\n\n{str(exception)}\n\n"
"Verifica che:\n"
"- Il file di input sia valido\n"
"- Pandoc sia installato correttamente\n"
"- Il formato di output sia supportato",
),
ErrorCategory.PERMISSION_ERROR: (
"Errore di Permessi",
f"Non è possibile accedere al file:\n\n{str(exception)}\n\n"
"Possibili cause:\n"
"- Il file è aperto in un altro programma\n"
"- Permessi insufficienti sulla cartella\n"
"- Il file è in sola lettura",
),
ErrorCategory.TIMEOUT_ERROR: (
"Timeout Conversione",
f"La conversione ha impiegato troppo tempo:\n\n{str(exception)}\n\n"
"Il documento potrebbe essere troppo grande o complesso.\n"
"Prova a chiudere altri programmi o dividere il documento.",
),
ErrorCategory.UNKNOWN: (
"Errore Inaspettato",
f"Si è verificato un errore imprevisto:\n\n{str(exception)}\n\n"
"Consulta il log per maggiori dettagli.",
),
}
return messages.get(category, messages[ErrorCategory.UNKNOWN])
def handle_conversion_error(
exception: Exception,
log_callback: Optional[Callable[[str], None]] = None,
show_dialog: bool = True,
) -> None:
"""
Handle a conversion error with consistent logging and user feedback.
Args:
exception: The exception to handle
log_callback: Optional callback function for logging to GUI (e.g., self._log)
show_dialog: If True, shows a messagebox to the user
"""
# Classify the error
category = classify_error(exception)
# Log the error with full stack trace
log.error(f"Conversion error ({category}): {exception}", exc_info=True)
# Log to GUI if callback provided
if log_callback:
log_callback(f"❌ ERRORE ({category}): {str(exception)}")
# Show user-friendly dialog if requested
if show_dialog:
title, message = get_user_friendly_message(category, exception)
messagebox.showerror(title, message)
def safe_conversion(
conversion_func: Callable,
*args,
log_callback: Optional[Callable[[str], None]] = None,
success_callback: Optional[Callable] = None,
error_callback: Optional[Callable[[Exception], None]] = None,
**kwargs,
) -> Optional[any]:
"""
Safely execute a conversion function with automatic error handling.
Args:
conversion_func: The conversion function to call
*args: Positional arguments for the conversion function
log_callback: Optional callback for GUI logging
success_callback: Optional callback on success
error_callback: Optional callback on error (receives the exception)
**kwargs: Keyword arguments for the conversion function
Returns:
The result of conversion_func if successful, None if failed
"""
try:
result = conversion_func(*args, **kwargs)
if success_callback:
success_callback(result)
return result
except Exception as e:
handle_conversion_error(e, log_callback=log_callback, show_dialog=True)
if error_callback:
error_callback(e)
return None