1
0
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:
Jonathan Harris 2018-01-29 01:34:30 +00:00
parent b9815400b3
commit a081b6b637
4 changed files with 183 additions and 2 deletions

View File

@ -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()

View File

@ -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
View File

@ -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
View 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()