File size: 5,300 Bytes
2197ab7
 
 
 
 
 
 
 
 
 
5fe3652
2197ab7
 
5fe3652
2197ab7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5fe3652
 
 
2197ab7
 
 
5fe3652
2197ab7
5fe3652
 
 
 
 
 
2197ab7
 
5fe3652
 
 
 
 
2197ab7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
"""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)