# 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