Done by temporarily buffering events before monitor.mode is set, then notifying plugins about them before sending the
first event where monitor.mode has a value. LoadGame is currently the only event that sets monitor.mode.
Fixes#2286
diff --git a/EDMarketConnector.py b/EDMarketConnector.py
index e7bdf490..d53b625d 100644
--- a/EDMarketConnector.py
+++ b/EDMarketConnector.py
@@ -22,7 +22,7 @@ import threading
import webbrowser
from os import chdir, environ
from time import localtime, strftime, time
-from typing import TYPE_CHECKING, Any, Literal
+from typing import TYPE_CHECKING, Any, Literal, MutableMapping
from constants import applongname, appname, protocolhandler_redirect
# Have this as early as possible for people running EDMarketConnector.exe
@@ -458,6 +458,8 @@ class AppWindow:
def __init__(self, master: tk.Tk): # noqa: C901, CCR001 # TODO - can possibly factor something out
+ self.early_journal_events: list[MutableMapping[str, Any]] = []
+
self.capi_query_holdoff_time = config.get_int('querytime', default=0) + companion.capi_query_cooldown
self.capi_fleetcarrier_query_holdoff_time = config.get_int('fleetcarrierquerytime', default=0) \
+ companion.capi_fleetcarrier_query_cooldown
@@ -1400,6 +1402,19 @@ class AppWindow:
self.cooldown()
logger.trace_if('capi.worker', '...done')
+ def handle_plugin_event_error(self, err: str | None) -> None:
+ """
+ Display a plugin's error message at the bottom of the main window.
+
+ If there was no error message passed in, nothing will be done.
+
+ :param err: Message to display, or None for no error.
+ """
+ if err:
+ self.status['text'] = err
+ if not config.get_int('hotkey_mute'):
+ hotkeymgr.play_bad()
+
def journal_event(self, event: str): # noqa: C901, CCR001 # Currently not easily broken up.
"""
Handle a Journal event passed through event queue from monitor.py.
@@ -1513,15 +1528,21 @@ class AppWindow:
if monitor.cmdr and monitor.mode == 'CQC' and entry['event']:
err = plug.notify_journal_entry_cqc(monitor.cmdr, monitor.is_beta, entry, monitor.state)
- if err:
- self.status['text'] = err
- if not config.get_int('hotkey_mute'):
- hotkeymgr.play_bad()
+ self.handle_plugin_event_error(err)
return # in CQC
if not entry['event'] or not monitor.mode:
+ # Game session has not fully loaded yet. We may be receiving events from the
+ # journal, but don't know which game mode (Open/CQC/solo etc) we're running in.
logger.trace_if('journal.queue', 'Startup, returning')
+
+ if entry['event']:
+ # Since we don't know which mode the game is in (and therefore whether to send
+ # the event to a plugin's `journal_event` or `journal_event_cqc` function),
+ # temporarily queue it so we can pass it on once the mode is known.
+ self.early_journal_events.append(entry)
+
return # Startup
if entry['event'] in ('StartUp', 'LoadGame') and monitor.started:
@@ -1538,6 +1559,25 @@ class AppWindow:
if not dashboard.start(self.w, monitor.started):
logger.info("Can't start Status monitoring")
+ # monitor.cmdr should always be set if monitor.mode is set (and monitor.mode must be set
+ # if we reach this point in the function), but we check monitor.cmdr here so the mypy
+ # doesn't complain about passing str|None where str is required.
+ if self.early_journal_events and monitor.cmdr:
+ for early_entry in self.early_journal_events:
+ if monitor.mode == 'CQC':
+ err = plug.notify_journal_entry_cqc(monitor.cmdr, monitor.is_beta, early_entry, monitor.state)
+ else:
+ err = plug.notify_journal_entry(
+ monitor.cmdr,
+ monitor.is_beta,
+ monitor.state['SystemName'],
+ monitor.state['StationName'],
+ early_entry,
+ monitor.state
+ )
+ self.handle_plugin_event_error(err)
+ self.early_journal_events.clear()
+
# Export loadout
if entry['event'] == 'Loadout' and not monitor.state['Captain'] \
and config.get_int('output') & config.OUT_SHIP:
@@ -1553,10 +1593,7 @@ class AppWindow:
monitor.state
)
- if err:
- self.status['text'] = err
- if not config.get_int('hotkey_mute'):
- hotkeymgr.play_bad()
+ self.handle_plugin_event_error(err)
auto_update = False
# Only if auth callback is not pending
@@ -1638,11 +1675,7 @@ class AppWindow:
# Currently we don't do anything with these events
if monitor.cmdr:
err = plug.notify_dashboard_entry(monitor.cmdr, monitor.is_beta, entry)
-
- if err:
- self.status['text'] = err
- if not config.get_int('hotkey_mute'):
- hotkeymgr.play_bad()
+ self.handle_plugin_event_error(err)
def plugin_error(self, event=None) -> None:
"""Display asynchronous error from plugin."""
Fixes a race identified where long plugin init times can result in
the following error being thrown in the journal monitor thread,
requiring a preferences reload to have journals continuing to be
processed:
```
Exception in thread Journal worker:
Traceback (most recent call last):
File "/home/testing/.pyenv/versions/3.11.9/lib/python3.11/threading.py", line 1045, in _bootstrap_inner
self.run()
File "/home/testing/.pyenv/versions/3.11.9/lib/python3.11/threading.py", line 982, in run
self._target(*self._args, **self._kwargs)
File "/home/testing/gitstuff/EDMarketConnector/monitor.py", line 461, in worker
self.root.event_generate('<<JournalEvent>>', when="tail")
File "/home/testing/.pyenv/versions/3.11.9/lib/python3.11/tkinter/__init__.py", line 1933, in event_generate
self.tk.call(args)
RuntimeError: main thread is not in main loop
```
Fixes a race identified where long plugin init times can result in
the following error being thrown in the journal monitor thread,
requiring a preferences reload to have journals continuing to be
processed:
```
Exception in thread Journal worker:
Traceback (most recent call last):
File "/home/testing/.pyenv/versions/3.11.9/lib/python3.11/threading.py", line 1045, in _bootstrap_inner
self.run()
File "/home/testing/.pyenv/versions/3.11.9/lib/python3.11/threading.py", line 982, in run
self._target(*self._args, **self._kwargs)
File "/home/testing/gitstuff/EDMarketConnector/monitor.py", line 461, in worker
self.root.event_generate('<<JournalEvent>>', when="tail")
File "/home/testing/.pyenv/versions/3.11.9/lib/python3.11/tkinter/__init__.py", line 1933, in event_generate
self.tk.call(args)
RuntimeError: main thread is not in main loop
```