"""Main entry point for the Fabric to Espanso conversion process.""" from typing import Optional import sys import signal import logging from contextlib import contextmanager from src.fabrics_processor.database import initialize_qdrant_database from src.fabrics_processor.file_change_detector import detect_file_changes from src.fabrics_processor.database_updater import update_qdrant_database from src.fabrics_processor.output_files_generator import generate_yaml_file from src.fabrics_processor.logger import setup_logger from src.fabrics_processor.config import config from src.fabrics_processor.deduplicator import remove_duplicates from src.fabrics_processor.exceptions import ( DatabaseConnectionError, DatabaseInitializationError ) # Setup logger logger = setup_logger() class GracefulExit(SystemExit): """Custom exception for graceful shutdown.""" pass def signal_handler(signum, frame): """Handle shutdown signals gracefully.""" logger.info(f"Received signal {signum}. Initiating graceful shutdown...") raise GracefulExit() @contextmanager def managed_qdrant_client(): """Context manager for handling Qdrant client lifecycle.""" client = None try: client = initialize_qdrant_database() yield client finally: if client: logger.info("Closing Qdrant client connection...") client.close() logger.info("Qdrant client connection closed") def process_changes(client) -> bool: """Process file changes and update database and YAML files. Args: client: Initialized Qdrant client Returns: bool: True if processing was successful, False otherwise """ try: # Detect file changes new_files, modified_files, deleted_files = detect_file_changes(client, config.fabric_patterns_folder) # Log the results if new_files: logger.info(f"New files: {[file['filename'] for file in new_files]}") if modified_files: logger.info(f"Modified files: {[file['filename'] for file in modified_files]}") if deleted_files: logger.info(f"Deleted files: {deleted_files}") # Track changes for summary duplicates_removed = 0 # Update database if there are changes if any([new_files, modified_files, deleted_files]): logger.info("Changes detected. Updating database...") update_qdrant_database(client, config.embedding.collection_name, new_files, modified_files, deleted_files) # Deduplicate entries after updating the database logger.info("Checking for and removing duplicate entries...") duplicates_removed = remove_duplicates(client, config.embedding.collection_name) if duplicates_removed > 0: logger.info(f"Removed {duplicates_removed} duplicate entries from the database") # Always generate output files to ensure consistency generate_yaml_file(client, config.yaml_output_folder) # Generate summary message total_entries = len(client.scroll(collection_name=config.embedding.collection_name, limit=10000)[0]) summary_message = f"Database update summary: {len(new_files)} added, {len(modified_files)} modified, {len(deleted_files)} deleted, {duplicates_removed} duplicates removed. Total entries: {total_entries}" logger.info(summary_message) return True except Exception as e: logger.error(f"Error processing changes: {str(e)}", exc_info=True) return False def main() -> Optional[int]: """Main application entry point. Returns: Optional[int]: Exit code, None if successful, 1 if error """ # Setup signal handlers signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) try: logger.info("Fabric to Espanso conversion process started") # Log configuration logger.info(f"Using configuration:") logger.info(f" Database URL: {config.database.url}") logger.info(f" Fabric patterns folder: {config.fabric_patterns_folder}") logger.info(f" YAML output folder: {config.yaml_output_folder}") logger.info(f" Obsidian textgenerator markdown output folder: {config.markdown_output_folder}") logger.info(f" Obsidian personal prompts input folder: {config.obsidian_input_folder}") # Process changes with managed client with managed_qdrant_client() as client: if process_changes(client): logger.info("Fabric to Espanso conversion completed successfully") return None else: logger.error("Fabric to Espanso conversion completed with errors") return 1 except GracefulExit: logger.info("Gracefully shutting down...") return None except (DatabaseConnectionError, DatabaseInitializationError) as e: logger.error(f"Database error: {str(e)}") return 1 except Exception as e: logger.error(f"Unexpected error: {str(e)}", exc_info=True) return 1 if __name__ == "__main__": sys.exit(main() or 0)