mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2025-04-14 16:27:13 +03:00
Monitor player status, and call new "status" plugin callback
This commit is contained in:
parent
b9815400b3
commit
a081b6b637
@ -57,6 +57,7 @@ import prefs
|
||||
import plug
|
||||
from hotkey import hotkeymgr
|
||||
from monitor import monitor
|
||||
from status import status
|
||||
from theme import theme
|
||||
|
||||
|
||||
@ -269,6 +270,7 @@ class AppWindow:
|
||||
self.w.bind('<KP_Enter>', self.getandsend)
|
||||
self.w.bind_all('<<Invoke>>', self.getandsend) # Hotkey monitoring
|
||||
self.w.bind_all('<<JournalEvent>>', self.journal_event) # Journal monitoring
|
||||
self.w.bind_all('<<StatusEvent>>', self.status_event) # Cmdr Status monitoring
|
||||
self.w.bind_all('<<PluginError>>', self.plugin_error) # Statusbar
|
||||
self.w.bind_all('<<Quit>>', self.onexit) # Updater
|
||||
|
||||
@ -627,6 +629,11 @@ class AppWindow:
|
||||
if not config.getint('hotkey_mute'):
|
||||
hotkeymgr.play_bad()
|
||||
|
||||
if entry['event'] in ['StartUp', 'LoadGame'] and monitor.started:
|
||||
# Can start status monitoring
|
||||
if not status.start(self.w, monitor.started):
|
||||
print "Can't start Status monitoring"
|
||||
|
||||
# Don't send to EDDN while on crew
|
||||
if monitor.state['Captain']:
|
||||
return
|
||||
@ -678,6 +685,17 @@ class AppWindow:
|
||||
if not config.getint('hotkey_mute'):
|
||||
hotkeymgr.play_bad()
|
||||
|
||||
# Handle Status event
|
||||
def status_event(self, event):
|
||||
entry = status.status
|
||||
if entry:
|
||||
# Currently we don't do anything with these events
|
||||
err = plug.notify_status(monitor.cmdr, monitor.is_beta, entry)
|
||||
if err:
|
||||
self.status['text'] = err
|
||||
if not config.getint('hotkey_mute'):
|
||||
hotkeymgr.play_bad()
|
||||
|
||||
# Display asynchronous error from plugin
|
||||
def plugin_error(self, event=None):
|
||||
if plug.last_error.get('msg'):
|
||||
@ -779,6 +797,7 @@ class AppWindow:
|
||||
config.set('geometry', '+{1}+{2}'.format(*self.w.geometry().split('+')))
|
||||
self.w.withdraw() # Following items can take a few seconds, so hide the main window while they happen
|
||||
hotkeymgr.unregister()
|
||||
status.close()
|
||||
monitor.close()
|
||||
plug.notify_stop()
|
||||
self.eddn.close()
|
||||
|
14
PLUGINS.md
14
PLUGINS.md
@ -106,7 +106,7 @@ this.status["text"] = "Happy!"
|
||||
|
||||
## Events
|
||||
|
||||
Once you have created your plugin and EDMC has loaded it there are two other functions you can define to be notified by EDMC when something happens: `journal_entry()` and `cmdr_data()`.
|
||||
Once you have created your plugin and EDMC has loaded it there are three other functions you can define to be notified by EDMC when something happens: `journal_entry()`, `status()` and `cmdr_data()`.
|
||||
|
||||
Your events all get called on the main tkinter loop so be sure not to block for very long or the EDMC will appear to freeze. If you have a long running operation then you should take a look at how to do background updates in tkinter - http://effbot.org/zone/tkinter-threads.htm
|
||||
|
||||
@ -128,6 +128,16 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
|
||||
sys.stderr.write("Arrived at {}\n".format(entry['StarSystem']))
|
||||
```
|
||||
|
||||
### Player Status
|
||||
|
||||
This gets called periodically - typically about once a second - whith the players live status
|
||||
|
||||
```python
|
||||
def status(cmdr, is_beta, entry):
|
||||
deployed = entry['Flags'] & 1<<6
|
||||
sys.stderr.write("Hardpoints {}\n", deployed and "deployed" or "stowed")
|
||||
```
|
||||
|
||||
### Getting Commander Data
|
||||
|
||||
This gets called when EDMC has just fetched fresh Cmdr and station data from Frontier's servers.
|
||||
@ -144,7 +154,7 @@ The data is a dictionary and full of lots of wonderful stuff!
|
||||
|
||||
## Error messages
|
||||
|
||||
You can display an error in EDMC's status area by returning a string from your `journal_entry()` or `cmdr_data()` function, or asynchronously (e.g. from a "worker" thread that is performing a long running operation) by calling `plug.show_error()`. Either method will cause the "bad" sound to be played (unless the user has muted sound).
|
||||
You can display an error in EDMC's status area by returning a string from your `journal_entry()`, `status()` or `cmdr_data()` function, or asynchronously (e.g. from a "worker" thread that is performing a long running operation) by calling `plug.show_error()`. Either method will cause the "bad" sound to be played (unless the user has muted sound).
|
||||
|
||||
The status area is shared between EDMC itself and all other plugins, so your message won't be displayed for very long. Create a dedicated widget if you need to display routine status information.
|
||||
|
||||
|
21
plug.py
21
plug.py
@ -226,6 +226,27 @@ def notify_journal_entry(cmdr, is_beta, system, station, entry, state):
|
||||
return error
|
||||
|
||||
|
||||
def notify_status(cmdr, is_beta, entry):
|
||||
"""
|
||||
Send a status entry to each plugin.
|
||||
:param cmdr: The piloting Cmdr name
|
||||
:param is_beta: whether the player is in a Beta universe.
|
||||
:param entry: The status entry as a dictionary
|
||||
:return: Error message from the first plugin that returns one (if any)
|
||||
"""
|
||||
error = None
|
||||
for plugin in PLUGINS:
|
||||
status = plugin._get_func('status')
|
||||
if status:
|
||||
try:
|
||||
# Pass a copy of the status entry in case the callee modifies it
|
||||
newerror = status(cmdr, is_beta, dict(entry))
|
||||
error = error or newerror
|
||||
except:
|
||||
print_exc()
|
||||
return error
|
||||
|
||||
|
||||
def notify_system_changed(timestamp, system, coordinates):
|
||||
"""
|
||||
Send notification data to each plugin when we arrive at a new system.
|
||||
|
131
status.py
Normal file
131
status.py
Normal file
@ -0,0 +1,131 @@
|
||||
import json
|
||||
from calendar import timegm
|
||||
from operator import itemgetter
|
||||
from os import listdir, stat
|
||||
from os.path import getmtime, isdir, join
|
||||
from sys import platform
|
||||
import time
|
||||
|
||||
if __debug__:
|
||||
from traceback import print_exc
|
||||
|
||||
from config import config
|
||||
|
||||
|
||||
if platform=='darwin':
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
|
||||
elif platform=='win32':
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
|
||||
else:
|
||||
# Linux's inotify doesn't work over CIFS or NFS, so poll
|
||||
FileSystemEventHandler = object # dummy
|
||||
|
||||
|
||||
# Status.json handler
|
||||
class Status(FileSystemEventHandler):
|
||||
|
||||
_POLL = 1 # Fallback polling interval
|
||||
|
||||
def __init__(self):
|
||||
FileSystemEventHandler.__init__(self) # futureproofing - not need for current version of watchdog
|
||||
self.root = None
|
||||
self.currentdir = None # The actual logdir that we're monitoring
|
||||
self.observer = None
|
||||
self.observed = None # a watchdog ObservedWatch, or None if polling
|
||||
self.status = {} # Current status for communicating status back to main thread
|
||||
|
||||
def start(self, root, started):
|
||||
self.root = root
|
||||
self.session_start = started
|
||||
|
||||
logdir = config.get('journaldir') or config.default_journal_dir
|
||||
if not logdir or not isdir(logdir):
|
||||
self.stop()
|
||||
return False
|
||||
|
||||
if self.currentdir and self.currentdir != logdir:
|
||||
self.stop()
|
||||
self.currentdir = logdir
|
||||
|
||||
# Set up a watchdog observer.
|
||||
# File system events are unreliable/non-existent over network drives on Linux.
|
||||
# We can't easily tell whether a path points to a network drive, so assume
|
||||
# any non-standard logdir might be on a network drive and poll instead.
|
||||
polling = bool(config.get('statusdir')) and platform != 'win32'
|
||||
if not polling and not self.observer:
|
||||
self.observer = Observer()
|
||||
self.observer.daemon = True
|
||||
self.observer.start()
|
||||
elif polling and self.observer:
|
||||
self.observer.stop()
|
||||
self.observer = None
|
||||
|
||||
if not self.observed and not polling:
|
||||
self.observed = self.observer.schedule(self, self.currentdir)
|
||||
|
||||
if __debug__:
|
||||
print '%s status "%s"' % (polling and 'Polling' or 'Monitoring', self.currentdir)
|
||||
|
||||
# Even if we're not intending to poll, poll at least once to process pre-existing
|
||||
# data and to check whether the watchdog thread has crashed due to events not
|
||||
# being supported on this filesystem.
|
||||
self.root.after(self._POLL * 1000/2, self.poll, True)
|
||||
|
||||
return True
|
||||
|
||||
def stop(self):
|
||||
if __debug__:
|
||||
print 'Stopping monitoring Status'
|
||||
self.currentdir = None
|
||||
if self.observed:
|
||||
self.observed = None
|
||||
self.observer.unschedule_all()
|
||||
self.status = {}
|
||||
|
||||
def close(self):
|
||||
self.stop()
|
||||
if self.observer:
|
||||
self.observer.stop()
|
||||
if self.observer:
|
||||
self.observer.join()
|
||||
self.observer = None
|
||||
|
||||
def poll(self, first_time=False):
|
||||
if not self.currentdir:
|
||||
# Stopped
|
||||
self.status = {}
|
||||
else:
|
||||
self.process()
|
||||
|
||||
if first_time:
|
||||
# Watchdog thread
|
||||
emitter = self.observed and self.observer._emitter_for_watch[self.observed] # Note: Uses undocumented attribute
|
||||
if emitter and emitter.is_alive():
|
||||
return # Watchdog thread still running - stop polling
|
||||
|
||||
self.root.after(self._POLL * 1000, self.poll) # keep polling
|
||||
|
||||
def on_modified(self, event):
|
||||
# watchdog callback - DirModifiedEvent on macOS, FileModifiedEvent on Windows
|
||||
if event.is_directory or stat(event.src_path).st_size: # Can get on_modified events when the file is emptied
|
||||
self.process(event.src_path if not event.is_directory else None)
|
||||
|
||||
# Can be called either in watchdog thread or, if polling, in main thread.
|
||||
def process(self, logfile=None):
|
||||
try:
|
||||
with open(join(self.currentdir, 'Status.json'), 'rb') as h:
|
||||
entry = json.load(h)
|
||||
|
||||
# Status file is shared between beta and live. So filter out status not in this game session.
|
||||
if timegm(time.strptime(entry['timestamp'], '%Y-%m-%dT%H:%M:%SZ')) >= self.session_start and self.status != entry:
|
||||
self.status = entry
|
||||
self.root.event_generate('<<StatusEvent>>', when="tail")
|
||||
except:
|
||||
if __debug__: print_exc()
|
||||
|
||||
# singleton
|
||||
status = Status()
|
Loading…
x
Reference in New Issue
Block a user