"""Sync classification results back to email providers.""" import logging from typing import List, Dict, Any, Optional from abc import ABC, abstractmethod from src.email_providers.base import ClassificationResult, BaseProvider logger = logging.getLogger(__name__) class ProviderSync(ABC): """Abstract base for syncing results back to providers.""" @abstractmethod def sync_classifications( self, results: List[ClassificationResult], category_to_label: Optional[Dict[str, str]] = None ) -> Dict[str, Any]: """ Sync classification results back to provider. Args: results: Classification results category_to_label: Map category names to provider labels Returns: Sync statistics """ pass class GmailSync(ProviderSync): """Sync results back to Gmail via labels.""" def __init__(self, provider): """Initialize Gmail sync.""" self.provider = provider if not hasattr(provider, 'update_labels'): raise ValueError("Provider must support update_labels") self.logger = logging.getLogger(__name__) def sync_classifications( self, results: List[ClassificationResult], category_to_label: Optional[Dict[str, str]] = None ) -> Dict[str, Any]: """Sync classifications as Gmail labels.""" if not category_to_label: # Default: use category name as label category_to_label = { 'junk': 'EmailSorter/Junk', 'transactional': 'EmailSorter/Transactional', 'auth': 'EmailSorter/Auth', 'newsletters': 'EmailSorter/Newsletters', 'social': 'EmailSorter/Social', 'automated': 'EmailSorter/Automated', 'conversational': 'EmailSorter/Conversational', 'work': 'EmailSorter/Work', 'personal': 'EmailSorter/Personal', 'finance': 'EmailSorter/Finance', 'travel': 'EmailSorter/Travel', 'unknown': 'EmailSorter/Unknown' } self.logger.info(f"Starting Gmail sync for {len(results)} results") # Build batch updates updates = [] synced_count = 0 failed_count = 0 for result in results: try: # Get label for category label = category_to_label.get(result.category) if not label: self.logger.debug(f"No label mapping for {result.category}") failed_count += 1 continue updates.append({ 'email_id': result.email_id, 'labels': [label] }) synced_count += 1 except Exception as e: self.logger.warning(f"Error syncing {result.email_id}: {e}") failed_count += 1 # Batch update via provider try: if updates: self.provider.batch_update(updates) self.logger.info(f"Synced {synced_count} emails to Gmail") except Exception as e: self.logger.error(f"Batch update failed: {e}") return { 'provider': 'gmail', 'synced': synced_count, 'failed': failed_count + len(results) - synced_count, 'error': str(e) } return { 'provider': 'gmail', 'synced': synced_count, 'failed': failed_count, 'total': len(results) } class IMAPSync(ProviderSync): """Sync results back to IMAP server via flags.""" def __init__(self, provider): """Initialize IMAP sync.""" self.provider = provider if not hasattr(provider, 'update_labels'): raise ValueError("Provider must support update_labels") self.logger = logging.getLogger(__name__) def sync_classifications( self, results: List[ClassificationResult], category_to_label: Optional[Dict[str, str]] = None ) -> Dict[str, Any]: """Sync classifications as IMAP flags/keywords.""" if not category_to_label: # Default: create IMAP keywords category_to_label = { 'junk': '$Junk', 'transactional': 'EmailSorter-Transactional', 'auth': 'EmailSorter-Auth', 'newsletters': 'EmailSorter-Newsletters', 'work': 'EmailSorter-Work', 'personal': 'EmailSorter-Personal', 'finance': 'EmailSorter-Finance', 'travel': 'EmailSorter-Travel', } self.logger.info(f"Starting IMAP sync for {len(results)} results") # Build batch updates updates = [] synced_count = 0 failed_count = 0 for result in results: try: # Get label for category label = category_to_label.get(result.category) if not label: self.logger.debug(f"No label mapping for {result.category}") continue updates.append({ 'email_id': result.email_id, 'labels': [label] }) synced_count += 1 except Exception as e: self.logger.warning(f"Error syncing {result.email_id}: {e}") failed_count += 1 # Batch update via provider try: if updates: self.provider.batch_update(updates) self.logger.info(f"Synced {synced_count} emails to IMAP") except Exception as e: self.logger.error(f"Batch update failed: {e}") return { 'provider': 'imap', 'synced': synced_count, 'failed': failed_count + len(results) - synced_count, 'error': str(e) } return { 'provider': 'imap', 'synced': synced_count, 'failed': failed_count, 'total': len(results) } def get_sync_handler(provider: BaseProvider) -> Optional[ProviderSync]: """Get appropriate sync handler for provider.""" provider_name = getattr(provider, 'name', 'unknown').lower() if 'gmail' in provider_name: return GmailSync(provider) elif 'imap' in provider_name: return IMAPSync(provider) else: logger.warning(f"No sync handler for provider: {provider_name}") return None