186 lines
6.2 KiB
Python
186 lines
6.2 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
|