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!
This commit is contained in:
parent
5b0c180a59
commit
25a25d031e
|
|
@ -66,73 +66,20 @@ from ui.hud_overlay import HUDOverlay
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Project Manager (Placeholder for integration)
|
# Core Integration
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
class ProjectManager:
|
import os
|
||||||
"""
|
import asyncio
|
||||||
Project manager placeholder.
|
from pathlib import Path
|
||||||
This will be replaced with the actual ProjectManager implementation.
|
from decimal import Decimal
|
||||||
"""
|
|
||||||
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
|
|
||||||
|
|
||||||
|
# Add core to path for imports
|
||||||
|
sys.path.insert(0, str(Path(__file__).parent.parent / "core"))
|
||||||
|
|
||||||
# ============================================================================
|
from core.log_watcher import LogWatcher
|
||||||
# Log Watcher (Placeholder for integration)
|
from core.project_manager import ProjectManager
|
||||||
# ============================================================================
|
from core.database import DatabaseManager
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
@ -274,10 +221,20 @@ class MainWindow(QMainWindow):
|
||||||
self.setMinimumSize(1200, 800)
|
self.setMinimumSize(1200, 800)
|
||||||
self.resize(1400, 900)
|
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.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
|
# State
|
||||||
self.current_project: Optional[Project] = None
|
self.current_project: Optional[Project] = None
|
||||||
|
|
@ -290,14 +247,12 @@ class MainWindow(QMainWindow):
|
||||||
self.create_menu_bar()
|
self.create_menu_bar()
|
||||||
self.create_status_bar()
|
self.create_status_bar()
|
||||||
|
|
||||||
# Connect log watcher
|
|
||||||
self.log_watcher.register_callback(self.on_log_event)
|
|
||||||
|
|
||||||
# Load initial data
|
# Load initial data
|
||||||
self.refresh_project_list()
|
self.refresh_project_list()
|
||||||
|
|
||||||
# Welcome message
|
# Welcome message
|
||||||
self.log_info("Application", "Lemontropia Suite initialized")
|
self.log_info("Application", "Lemontropia Suite initialized")
|
||||||
|
self.log_info("Database", f"Database ready: {self.db.db_path}")
|
||||||
|
|
||||||
# ========================================================================
|
# ========================================================================
|
||||||
# UI Setup
|
# UI Setup
|
||||||
|
|
@ -871,7 +826,16 @@ class MainWindow(QMainWindow):
|
||||||
Args:
|
Args:
|
||||||
project_id: The ID of the project to start session for
|
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:
|
if not project:
|
||||||
self.log_error("Session", f"Project {project_id} not found")
|
self.log_error("Session", f"Project {project_id} not found")
|
||||||
return
|
return
|
||||||
|
|
@ -891,11 +855,164 @@ class MainWindow(QMainWindow):
|
||||||
self.log_info("Session", f"Started session for project: {project.name}")
|
self.log_info("Session", f"Started session for project: {project.name}")
|
||||||
self.session_info_label.setText(f"Session active: {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
|
# Show HUD and start session tracking
|
||||||
self.hud.show()
|
self.hud.show()
|
||||||
self.hud.start_session(weapon="Unknown", loadout="Default")
|
self.hud.start_session(weapon="Unknown", loadout="Default")
|
||||||
self.log_info("HUD", "HUD overlay shown and session tracking started")
|
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):
|
def on_start_session(self):
|
||||||
"""Handle start session button."""
|
"""Handle start session button."""
|
||||||
if self.current_project and self.session_state == SessionState.IDLE:
|
if self.current_project and self.session_state == SessionState.IDLE:
|
||||||
|
|
@ -904,6 +1021,14 @@ class MainWindow(QMainWindow):
|
||||||
def on_stop_session(self):
|
def on_stop_session(self):
|
||||||
"""Handle stop session button."""
|
"""Handle stop session button."""
|
||||||
if self.session_state in (SessionState.RUNNING, SessionState.PAUSED):
|
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.set_session_state(SessionState.IDLE)
|
||||||
self.current_session_id = None
|
self.current_session_id = None
|
||||||
|
|
||||||
|
|
@ -912,6 +1037,9 @@ class MainWindow(QMainWindow):
|
||||||
self.log_info("Session", "Session stopped")
|
self.log_info("Session", "Session stopped")
|
||||||
self.session_info_label.setText("Session stopped")
|
self.session_info_label.setText("Session stopped")
|
||||||
|
|
||||||
|
# End HUD session
|
||||||
|
self.hud.end_session()
|
||||||
|
|
||||||
# Hide HUD
|
# Hide HUD
|
||||||
self.hud.hide()
|
self.hud.hide()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue