"""Output Service - Business Logic for Output Management. Handles output file operations and tracking. """ import logging from typing import List, Optional from src.database.models import Output from src.utils import file_utils logger = logging.getLogger(__name__) class OutputService: """Service class for output management business logic. Provides clean interface for managing output files and their database records. """ def __init__(self, db_manager): """Initialize the output service. Args: db_manager: DatabaseManager instance. """ self.db_manager = db_manager def save_output(self, recipe_id: int, source_file_path: str, execution_notes: str = "") -> Optional[Output]: """Save an output file for a recipe. Copies file to recipe output directory and creates database record. Args: recipe_id: ID of the associated recipe. source_file_path: Path to the source file to copy. execution_notes: Notes about this execution. Returns: Output instance if successful, None otherwise. """ try: # Use file_utils to save output file output = file_utils.save_output_file( self.db_manager, source_file_path, recipe_id, execution_notes ) if output: logger.info(f"Saved output for recipe {recipe_id}: {output.filename}") else: logger.error(f"Failed to save output for recipe {recipe_id}") return output except Exception as e: logger.error(f"Error saving output: {e}", exc_info=True) return None def get_recipe_outputs(self, recipe_id: int) -> List[Output]: """Get all outputs for a specific recipe. Args: recipe_id: Recipe ID. Returns: List of outputs for the recipe, sorted by generation date (newest first). """ try: outputs = Output.get_by_recipe(self.db_manager, recipe_id) logger.debug(f"Retrieved {len(outputs)} outputs for recipe {recipe_id}") return outputs except Exception as e: logger.error(f"Error retrieving outputs for recipe {recipe_id}: {e}", exc_info=True) return [] def get_all_outputs(self) -> List[Output]: """Get all outputs across all recipes. Returns: List of all outputs. """ try: outputs = Output.get_all(self.db_manager) logger.debug(f"Retrieved {len(outputs)} total outputs") return outputs except Exception as e: logger.error(f"Error retrieving all outputs: {e}", exc_info=True) return [] def delete_output(self, output_id: int) -> bool: """Delete an output file and its database record. Args: output_id: ID of output to delete. Returns: bool: True if successful, False otherwise. """ try: # Load output to get filepath output = Output.get_by_id(self.db_manager, output_id) if not output: logger.warning(f"Output {output_id} not found for deletion") return False # Delete file from filesystem file_deleted = file_utils.delete_output_file(output.filepath) if not file_deleted: logger.warning(f"Failed to delete file: {output.filepath}") # Continue to delete DB record even if file deletion failed # Delete database record db_deleted = Output.delete(self.db_manager, output_id) if db_deleted: logger.info(f"Deleted output {output_id}: {output.filename}") return True else: logger.error(f"Failed to delete output record {output_id}") return False except Exception as e: logger.error(f"Error deleting output {output_id}: {e}", exc_info=True) return False def open_output(self, output_id: int) -> bool: """Open an output file with the system default application. Args: output_id: ID of output to open. Returns: bool: True if successful, False otherwise. """ try: # Load output to get filepath output = Output.get_by_id(self.db_manager, output_id) if not output: logger.error(f"Output {output_id} not found") return False # Open file using file_utils success = file_utils.open_file(output.filepath) if success: logger.info(f"Opened output {output_id}: {output.filename}") else: logger.error(f"Failed to open output {output_id}: {output.filename}") return success except Exception as e: logger.error(f"Error opening output {output_id}: {e}", exc_info=True) return False def search_outputs(self, query: str) -> List[Output]: """Search outputs by filename. Args: query: Search query string. Returns: List of matching outputs. """ try: if not query or not query.strip(): # Empty query returns all outputs return self.get_all_outputs() # Get all outputs and filter by filename all_outputs = self.get_all_outputs() query_lower = query.strip().lower() matching = [ output for output in all_outputs if query_lower in output.filename.lower() ] logger.debug(f"Search '{query}' found {len(matching)} outputs") return matching except Exception as e: logger.error(f"Error searching outputs: {e}", exc_info=True) return [] def get_output_by_id(self, output_id: int) -> Optional[Output]: """Get an output by ID. Args: output_id: Output ID. Returns: Output instance or None. """ try: output = Output.get_by_id(self.db_manager, output_id) if output: logger.debug(f"Retrieved output {output_id}: {output.filename}") else: logger.debug(f"Output {output_id} not found") return output except Exception as e: logger.error(f"Error retrieving output {output_id}: {e}", exc_info=True) return None def update_execution_notes(self, output_id: int, notes: str) -> bool: """Update execution notes for an output. Args: output_id: ID of output to update. notes: New execution notes. Returns: bool: True if successful, False otherwise. """ try: success = Output.update( self.db_manager, output_id, execution_notes=notes ) if success: logger.info(f"Updated execution notes for output {output_id}") else: logger.error(f"Failed to update execution notes for output {output_id}") return success except Exception as e: logger.error(f"Error updating execution notes: {e}", exc_info=True) return False