fix(gui): SQLite thread safety - use queue for cross-thread database access
- LogWatcher callbacks now queue events instead of direct DB access - Main thread processes queue every 100ms - Fixes 'SQLite objects created in a thread can only be used in that same thread' error - Loot will now be properly recorded to database
This commit is contained in:
parent
9f03b3c610
commit
59094ee469
|
|
@ -236,10 +236,20 @@ class MainWindow(QMainWindow):
|
||||||
self.log_watcher: Optional[LogWatcher] = None
|
self.log_watcher: Optional[LogWatcher] = None
|
||||||
self._log_watcher_task = None
|
self._log_watcher_task = None
|
||||||
|
|
||||||
|
# Thread-safe queue for cross-thread communication
|
||||||
|
from queue import Queue
|
||||||
|
self._event_queue = Queue()
|
||||||
|
|
||||||
|
# Timer to process queued events in main thread
|
||||||
|
self._queue_timer = QTimer(self)
|
||||||
|
self._queue_timer.timeout.connect(self._process_queued_events)
|
||||||
|
self._queue_timer.start(100) # Check every 100ms
|
||||||
|
|
||||||
# State
|
# State
|
||||||
self.current_project: Optional[Project] = None
|
self.current_project: Optional[Project] = None
|
||||||
self.session_state = SessionState.IDLE
|
self.session_state = SessionState.IDLE
|
||||||
self.current_session_id: Optional[int] = None
|
self.current_session_id: Optional[int] = None
|
||||||
|
self._current_db_session_id: Optional[int] = None
|
||||||
|
|
||||||
# Setup UI
|
# Setup UI
|
||||||
self.setup_ui()
|
self.setup_ui()
|
||||||
|
|
@ -904,27 +914,28 @@ class MainWindow(QMainWindow):
|
||||||
"""Handle loot events."""
|
"""Handle loot events."""
|
||||||
item_name = event.data.get('item_name', 'Unknown')
|
item_name = event.data.get('item_name', 'Unknown')
|
||||||
value_ped = event.data.get('value_ped', Decimal('0.0'))
|
value_ped = event.data.get('value_ped', Decimal('0.0'))
|
||||||
|
quantity = event.data.get('quantity', 1)
|
||||||
|
|
||||||
# Skip Universal Ammo
|
# Skip Universal Ammo
|
||||||
if item_name == 'Universal Ammo':
|
if item_name == 'Universal Ammo':
|
||||||
return
|
return
|
||||||
|
|
||||||
# Record to database
|
# Queue database write for main thread (SQLite thread safety)
|
||||||
if self._current_db_session_id:
|
if self._current_db_session_id:
|
||||||
loot = LootEvent(
|
self._event_queue.put({
|
||||||
item_name=item_name,
|
'type': 'loot',
|
||||||
quantity=event.data.get('quantity', 1),
|
'session_id': self._current_db_session_id,
|
||||||
value_ped=value_ped,
|
'item_name': item_name,
|
||||||
event_type='regular',
|
'quantity': quantity,
|
||||||
raw_log_line=event.raw_line
|
'value_ped': value_ped,
|
||||||
)
|
'raw_line': event.raw_line
|
||||||
self.project_manager.record_loot(loot)
|
})
|
||||||
|
|
||||||
# Update HUD
|
# Update HUD (thread-safe)
|
||||||
self.hud.on_loot_event(item_name, value_ped)
|
self.hud.on_loot_event(item_name, value_ped)
|
||||||
|
|
||||||
# Log to UI
|
# Log to UI (main thread only - use signal/slot or queue)
|
||||||
self.log_info("Loot", f"{item_name} x{event.data.get('quantity', 1)} ({value_ped} PED)")
|
# We'll log this in _process_queued_events instead
|
||||||
|
|
||||||
def on_global(event):
|
def on_global(event):
|
||||||
"""Handle global events."""
|
"""Handle global events."""
|
||||||
|
|
@ -1017,6 +1028,34 @@ class MainWindow(QMainWindow):
|
||||||
self._log_watcher_thread = None
|
self._log_watcher_thread = None
|
||||||
self.log_info("LogWatcher", "Stopped")
|
self.log_info("LogWatcher", "Stopped")
|
||||||
|
|
||||||
|
def _process_queued_events(self):
|
||||||
|
"""Process events from the queue in the main thread (SQLite thread safety)."""
|
||||||
|
from core.project_manager import LootEvent
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
processed = 0
|
||||||
|
while not self._event_queue.empty() and processed < 10: # Process max 10 per tick
|
||||||
|
try:
|
||||||
|
event = self._event_queue.get_nowait()
|
||||||
|
|
||||||
|
if event['type'] == 'loot':
|
||||||
|
# Record to database (now in main thread - safe)
|
||||||
|
loot = LootEvent(
|
||||||
|
item_name=event['item_name'],
|
||||||
|
quantity=event['quantity'],
|
||||||
|
value_ped=event['value_ped'],
|
||||||
|
event_type='regular',
|
||||||
|
raw_log_line=event['raw_line']
|
||||||
|
)
|
||||||
|
self.project_manager.record_loot(loot)
|
||||||
|
|
||||||
|
# Log to UI
|
||||||
|
self.log_info("Loot", f"{event['item_name']} x{event['quantity']} ({event['value_ped']} PED)")
|
||||||
|
|
||||||
|
processed += 1
|
||||||
|
except Exception as e:
|
||||||
|
self.log_error("EventQueue", f"Error processing event: {e}")
|
||||||
|
|
||||||
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:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue