From 25a25d031ee20b1b5924ba9e0efaadd210d8c2e7 Mon Sep 17 00:00:00 2001 From: LemonNexus Date: Sun, 8 Feb 2026 22:03:06 +0000 Subject: [PATCH] fix(gui): integrate real LogWatcher and ProjectManager into MainWindow - Replace placeholder classes with real core.database, core.project_manager, core.log_watcher - Add LogWatcher lifecycle management (start/stop in background thread) - Connect log events to HUD updates (loot, damage, globals, etc.) - Add database session tracking - Fix session stop to properly end database session and stop LogWatcher This enables real-time log parsing in the GUI! --- ui/main_window.py | 268 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 198 insertions(+), 70 deletions(-) diff --git a/ui/main_window.py b/ui/main_window.py index 13459d1..1852ad2 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -66,73 +66,20 @@ from ui.hud_overlay import HUDOverlay # ============================================================================ -# Project Manager (Placeholder for integration) +# Core Integration # ============================================================================ -class ProjectManager: - """ - Project manager placeholder. - This will be replaced with the actual ProjectManager implementation. - """ - def __init__(self): - self._projects: List[Project] = [ - Project(1, "Default Project", "Default automation project", session_count=5), - Project(2, "Farming Bot", "Resource farming automation", session_count=12), - Project(3, "Quest Helper", "Quest automation helper", session_count=3), - ] - self._next_id = 4 - - def get_all_projects(self) -> List[Project]: - """Get all projects.""" - return self._projects.copy() - - def get_project(self, project_id: int) -> Optional[Project]: - """Get project by ID.""" - for proj in self._projects: - if proj.id == project_id: - return proj - return None - - def create_project(self, name: str, description: str = "") -> Project: - """Create a new project.""" - project = Project( - id=self._next_id, - name=name, - description=description, - created_at=datetime.now(), - session_count=0 - ) - self._projects.append(project) - self._next_id += 1 - return project +import os +import asyncio +from pathlib import Path +from decimal import Decimal +# Add core to path for imports +sys.path.insert(0, str(Path(__file__).parent.parent / "core")) -# ============================================================================ -# Log Watcher (Placeholder for integration) -# ============================================================================ - -class LogWatcher: - """ - Log watcher placeholder. - This will be replaced with the actual LogWatcher implementation. - """ - def __init__(self): - self._callbacks: List[Callable[[LogEvent], None]] = [] - self._running = False - - def register_callback(self, callback: Callable[[LogEvent], None]): - """Register a callback for log events.""" - self._callbacks.append(callback) - - def unregister_callback(self, callback: Callable[[LogEvent], None]): - """Unregister a callback.""" - if callback in self._callbacks: - self._callbacks.remove(callback) - - def emit(self, event: LogEvent): - """Emit a log event to all registered callbacks.""" - for callback in self._callbacks: - callback(event) +from core.log_watcher import LogWatcher +from core.project_manager import ProjectManager +from core.database import DatabaseManager # ============================================================================ @@ -274,10 +221,20 @@ class MainWindow(QMainWindow): self.setMinimumSize(1200, 800) self.resize(1400, 900) - # Initialize components + # Initialize database and real project manager + self.db = DatabaseManager() + if not self.db.initialize(): + QMessageBox.critical(self, "Error", "Failed to initialize database!") + sys.exit(1) + + self.project_manager = ProjectManager(self.db) + + # Initialize HUD self.hud = HUDOverlay() - self.project_manager = ProjectManager() - self.log_watcher = LogWatcher() + + # Log watcher - created when session starts + self.log_watcher: Optional[LogWatcher] = None + self._log_watcher_task = None # State self.current_project: Optional[Project] = None @@ -290,14 +247,12 @@ class MainWindow(QMainWindow): self.create_menu_bar() self.create_status_bar() - # Connect log watcher - self.log_watcher.register_callback(self.on_log_event) - # Load initial data self.refresh_project_list() # Welcome message self.log_info("Application", "Lemontropia Suite initialized") + self.log_info("Database", f"Database ready: {self.db.db_path}") # ======================================================================== # UI Setup @@ -871,7 +826,16 @@ class MainWindow(QMainWindow): Args: project_id: The ID of the project to start session for """ - project = self.project_manager.get_project(project_id) + from core.project_manager import ProjectData + + # Get real project from database + projects = self.project_manager.list_projects() + project = None + for p in projects: + if p.id == project_id: + project = p + break + if not project: self.log_error("Session", f"Project {project_id} not found") return @@ -891,11 +855,164 @@ class MainWindow(QMainWindow): self.log_info("Session", f"Started session for project: {project.name}") self.session_info_label.setText(f"Session active: {project.name}") + # Start real session in database + session = self.project_manager.start_session(project_id) + self._current_db_session_id = session.id if session else None + + # Setup LogWatcher + use_mock = os.getenv('USE_MOCK_DATA', 'false').lower() in ('true', '1', 'yes') + log_path = os.getenv('EU_CHAT_LOG_PATH', '') + + if use_mock or not log_path: + # Use mock log for testing + test_data_dir = Path(__file__).parent.parent / "test-data" + test_data_dir.mkdir(exist_ok=True) + mock_log = test_data_dir / "mock-chat.log" + if not mock_log.exists(): + from core.log_watcher import MockLogGenerator + MockLogGenerator.create_mock_file(mock_log, lines=50) + self.log_watcher = LogWatcher(str(mock_log), poll_interval=2.0, mock_mode=True) + self.log_info("LogWatcher", "Using MOCK data for testing") + else: + self.log_watcher = LogWatcher(log_path, poll_interval=1.0, mock_mode=False) + self.log_info("LogWatcher", f"Using REAL log: {log_path}") + + # Subscribe to events + self._setup_log_watcher_callbacks() + + # Start LogWatcher in background + self._start_log_watcher() + # Show HUD and start session tracking self.hud.show() self.hud.start_session(weapon="Unknown", loadout="Default") self.log_info("HUD", "HUD overlay shown and session tracking started") + def _setup_log_watcher_callbacks(self): + """Setup LogWatcher event callbacks.""" + if not self.log_watcher: + return + + from core.project_manager import LootEvent + from decimal import Decimal + + def on_loot(event): + """Handle loot events.""" + item_name = event.data.get('item_name', 'Unknown') + value_ped = event.data.get('value_ped', Decimal('0.0')) + + # Skip Universal Ammo + if item_name == 'Universal Ammo': + return + + # Record to database + if self._current_db_session_id: + loot = LootEvent( + item_name=item_name, + quantity=event.data.get('quantity', 1), + value_ped=value_ped, + event_type='regular', + raw_log_line=event.raw_line + ) + self.project_manager.record_loot(loot) + + # Update HUD + self.hud.on_loot_event(item_name, value_ped) + + # Log to UI + self.log_info("Loot", f"{item_name} x{event.data.get('quantity', 1)} ({value_ped} PED)") + + def on_global(event): + """Handle global events.""" + value_ped = event.data.get('value_ped', Decimal('0.0')) + player = event.data.get('player_name', 'Unknown') + self.hud.on_global(value_ped) + self.log_info("Global", f"{player} found {value_ped} PED!") + + def on_personal_global(event): + """Handle personal global events.""" + value_ped = event.data.get('value_ped', Decimal('0.0')) + creature = event.data.get('creature', 'Unknown') + self.hud.on_global(value_ped) + self.log_info("Global", f"🎉 YOUR GLOBAL: {creature} for {value_ped} PED!!!") + + def on_hof(event): + """Handle HoF events.""" + value_ped = event.data.get('value_ped', Decimal('0.0')) + self.hud.on_hof(value_ped) + self.log_info("HoF", f"🏆 HALL OF FAME: {value_ped} PED!") + + def on_skill(event): + """Handle skill events.""" + skill_name = event.data.get('skill_name', 'Unknown') + gained = event.data.get('gained', 0) + self.log_info("Skill", f"{skill_name} +{gained}") + + def on_damage_dealt(event): + """Handle damage dealt.""" + damage = event.data.get('damage', 0) + self.hud.on_damage_dealt(float(damage)) + + def on_damage_taken(event): + """Handle damage taken.""" + damage = event.data.get('damage', 0) + self.hud.on_damage_taken(float(damage)) + + def on_evade(event): + """Handle evade.""" + evade_type = event.data.get('type', 'Evade') + self.log_info("Evade", evade_type) + + # Subscribe to all event types + self.log_watcher.subscribe('loot', on_loot) + self.log_watcher.subscribe('global', on_global) + self.log_watcher.subscribe('personal_global', on_personal_global) + self.log_watcher.subscribe('hof', on_hof) + self.log_watcher.subscribe('skill', on_skill) + self.log_watcher.subscribe('damage_dealt', on_damage_dealt) + self.log_watcher.subscribe('critical_hit', on_damage_dealt) # Count as damage + self.log_watcher.subscribe('damage_taken', on_damage_taken) + self.log_watcher.subscribe('evade', on_evade) + + def _start_log_watcher(self): + """Start LogWatcher in background thread.""" + import asyncio + from PyQt6.QtCore import QThread + + class LogWatcherThread(QThread): + def __init__(self, watcher): + super().__init__() + self.watcher = watcher + self._running = True + + def run(self): + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + try: + loop.run_until_complete(self.watcher.start()) + while self._running: + loop.run_until_complete(asyncio.sleep(0.1)) + except Exception as e: + print(f"LogWatcher error: {e}") + finally: + loop.run_until_complete(self.watcher.stop()) + loop.close() + + def stop(self): + self._running = False + + self._log_watcher_thread = LogWatcherThread(self.log_watcher) + self._log_watcher_thread.start() + self.log_info("LogWatcher", "Started watching for events") + + def _stop_log_watcher(self): + """Stop LogWatcher.""" + if hasattr(self, '_log_watcher_thread') and self._log_watcher_thread: + self._log_watcher_thread.stop() + self._log_watcher_thread.wait(2000) # Wait up to 2 seconds + self._log_watcher_thread = None + self.log_info("LogWatcher", "Stopped") + def on_start_session(self): """Handle start session button.""" if self.current_project and self.session_state == SessionState.IDLE: @@ -904,6 +1021,14 @@ class MainWindow(QMainWindow): def on_stop_session(self): """Handle stop session button.""" if self.session_state in (SessionState.RUNNING, SessionState.PAUSED): + # Stop LogWatcher + self._stop_log_watcher() + + # End session in database + if self._current_db_session_id: + self.project_manager.end_session(self._current_db_session_id) + self._current_db_session_id = None + self.set_session_state(SessionState.IDLE) self.current_session_id = None @@ -912,6 +1037,9 @@ class MainWindow(QMainWindow): self.log_info("Session", "Session stopped") self.session_info_label.setText("Session stopped") + # End HUD session + self.hud.end_session() + # Hide HUD self.hud.hide()