mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2025-04-21 11:27:38 +03:00
Monitor the journal file and send events to EDDN
Sends 'FSDJump', 'Docked' and 'Scan' events in draft http://schemas.elite-markets.net/eddn/journal/1 format.
This commit is contained in:
parent
0ec2a532a0
commit
4583f0e316
@ -9,7 +9,8 @@ from os import mkdir
|
||||
from os.path import expanduser, isdir, join
|
||||
import re
|
||||
import requests
|
||||
from time import time, localtime, strftime
|
||||
from time import time, localtime, strftime, strptime
|
||||
from calendar import timegm
|
||||
|
||||
import Tkinter as tk
|
||||
import ttk
|
||||
@ -75,9 +76,6 @@ class AppWindow:
|
||||
self.w.rowconfigure(0, weight=1)
|
||||
self.w.columnconfigure(0, weight=1)
|
||||
|
||||
# Special handling for overrideredict
|
||||
self.w.bind("<Map>", self.onmap)
|
||||
|
||||
plug.load_plugins()
|
||||
|
||||
if platform != 'darwin':
|
||||
@ -242,6 +240,9 @@ class AppWindow:
|
||||
theme.register_highlight(self.station)
|
||||
theme.apply(self.w)
|
||||
|
||||
# Special handling for overrideredict
|
||||
self.w.bind("<Map>", self.onmap)
|
||||
|
||||
# Load updater after UI creation (for WinSparkle)
|
||||
import update
|
||||
self.updater = update.Updater(self.w)
|
||||
@ -252,8 +253,7 @@ class AppWindow:
|
||||
hotkeymgr.register(self.w, config.getint('hotkey_code'), config.getint('hotkey_mods'))
|
||||
|
||||
# Install log monitoring
|
||||
monitor.set_callback('Dock', self.getandsend)
|
||||
monitor.set_callback('Jump', self.system_change)
|
||||
self.w.bind_all('<<JournalEvent>>', self.journal_event) # user-generated
|
||||
monitor.start(self.w)
|
||||
|
||||
# First run
|
||||
@ -394,9 +394,14 @@ class AppWindow:
|
||||
pass
|
||||
|
||||
elif not data['commander'].get('docked'):
|
||||
# signal as error because the user might actually be docked but the server hosting the Companion API hasn't caught up
|
||||
if not self.status['text']:
|
||||
self.status['text'] = _("You're not docked at a station!")
|
||||
if not event and not retrying:
|
||||
# Silently retry if we got here by 'Automatically update on docking' and the server hasn't caught up
|
||||
self.w.after(int(SERVER_RETRY * 1000), lambda:self.getandsend(event, True))
|
||||
return # early exit to avoid starting cooldown count
|
||||
else:
|
||||
# Signal as error because the user might actually be docked but the server hosting the Companion API hasn't caught up
|
||||
if not self.status['text']:
|
||||
self.status['text'] = _("You're not docked at a station!")
|
||||
|
||||
else:
|
||||
# Finally - the data looks sane and we're docked at a station
|
||||
@ -479,31 +484,62 @@ class AppWindow:
|
||||
except:
|
||||
pass
|
||||
|
||||
def system_change(self, event, timestamp, system, coordinates):
|
||||
# Handle event(s) from the journal
|
||||
def journal_event(self, event):
|
||||
while True:
|
||||
entry = monitor.get_entry()
|
||||
if entry is None:
|
||||
return
|
||||
system_changed = monitor.system and self.system['text'] != monitor.system
|
||||
station_changed = monitor.station and self.station['text'] != monitor.station
|
||||
|
||||
if self.system['text'] != system:
|
||||
self.system['text'] = system
|
||||
# Update main window
|
||||
self.cmdr['text'] = monitor.cmdr or ''
|
||||
self.system['text'] = monitor.system or ''
|
||||
self.station['text'] = monitor.station or (EDDB.system(monitor.system) and self.STATION_UNDOCKED or '')
|
||||
|
||||
self.system['image'] = ''
|
||||
self.station['text'] = EDDB.system(system) and self.STATION_UNDOCKED or ''
|
||||
plug.notify_journal_entry(monitor.cmdr, monitor.system, monitor.station, entry)
|
||||
|
||||
plug.notify_system_changed(timestamp, system, coordinates)
|
||||
if system_changed:
|
||||
self.system['image'] = ''
|
||||
timestamp = timegm(strptime(entry['timestamp'], '%Y-%m-%dT%H:%M:%SZ'))
|
||||
|
||||
if config.getint('output') & config.OUT_SYS_EDSM:
|
||||
try:
|
||||
self.status['text'] = _('Sending data to EDSM...')
|
||||
self.w.update_idletasks()
|
||||
self.edsm.writelog(timestamp, system, coordinates) # Do EDSM lookup during EDSM export
|
||||
# Backwards compatibility
|
||||
plug.notify_system_changed(timestamp, monitor.system, monitor.coordinates)
|
||||
|
||||
# Update EDSM if we have coordinates - i.e. Location or FSDJump events
|
||||
if config.getint('output') & config.OUT_SYS_EDSM and monitor.coordinates:
|
||||
try:
|
||||
self.status['text'] = _('Sending data to EDSM...')
|
||||
self.w.update_idletasks()
|
||||
self.edsm.writelog(timestamp, monitor.system, monitor.coordinates)
|
||||
self.status['text'] = strftime(_('Last updated at {HH}:{MM}:{SS}').format(HH='%H', MM='%M', SS='%S').encode('utf-8'), localtime(timestamp)).decode('utf-8')
|
||||
except Exception as e:
|
||||
if __debug__: print_exc()
|
||||
self.status['text'] = unicode(e)
|
||||
if not config.getint('hotkey_mute'):
|
||||
hotkeymgr.play_bad()
|
||||
else:
|
||||
self.edsm.link(monitor.system)
|
||||
self.status['text'] = strftime(_('Last updated at {HH}:{MM}:{SS}').format(HH='%H', MM='%M', SS='%S').encode('utf-8'), localtime(timestamp)).decode('utf-8')
|
||||
except Exception as e:
|
||||
if __debug__: print_exc()
|
||||
self.status['text'] = unicode(e)
|
||||
if not config.getint('hotkey_mute'):
|
||||
hotkeymgr.play_bad()
|
||||
else:
|
||||
self.edsm.link(system)
|
||||
self.status['text'] = strftime(_('Last updated at {HH}:{MM}:{SS}').format(HH='%H', MM='%M', SS='%S').encode('utf-8'), localtime(timestamp)).decode('utf-8')
|
||||
self.edsmpoll()
|
||||
self.edsmpoll()
|
||||
|
||||
# Send interesting events to EDDN
|
||||
if (config.getint('output') & config.OUT_SYS_EDDN and monitor.cmdr and
|
||||
(entry['event'] == 'FSDJump' and system_changed or
|
||||
entry['event'] == 'Docked' and station_changed or
|
||||
entry['event'] == 'Scan')):
|
||||
# strip out properties disallowed by the schema
|
||||
for thing in ['CockpitBreach', 'BoostUsed', 'FuelLevel', 'FuelUsed', 'JumpDist']:
|
||||
entry.pop(thing, None)
|
||||
for thing in entry.keys():
|
||||
if thing.endswith('_Localised'):
|
||||
entry.pop(thing, None)
|
||||
eddn.export_journal_entry(monitor.cmdr, monitor.is_beta, entry)
|
||||
|
||||
# Auto-Update after docking
|
||||
if station_changed and config.getint('output') & (config.OUT_MKT_EDDN|config.OUT_MKT_MANUAL) == config.OUT_MKT_EDDN and entry['event'] == 'Docked':
|
||||
self.w.after(int(SERVER_RETRY * 1000), self.getandsend)
|
||||
|
||||
def edsmpoll(self):
|
||||
result = self.edsm.result
|
||||
|
18
PLUGINS.md
18
PLUGINS.md
@ -68,21 +68,23 @@ Once you have created your plugin and EDMC has loaded it there are two other fun
|
||||
|
||||
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
|
||||
|
||||
### Arriving in a System
|
||||
### Journal Entry
|
||||
|
||||
This gets called when EDMC uses the netlog to notice that you have arrived at a new star system.
|
||||
This gets called when EDMC sees a new entry in the game's journal.
|
||||
|
||||
```
|
||||
def system_changed(timestamp, system, coordinates):
|
||||
"""
|
||||
We arrived at a new system!
|
||||
"""
|
||||
sys.stderr.write("{} {}".format(timestamp, system))
|
||||
def journal_entry(cmdr, system, station, entry):
|
||||
if entry['event'] == 'FSDJump':
|
||||
# We arrived at a new system!
|
||||
if 'StarPos' in entry:
|
||||
sys.stderr.write("Arrived at {} ({},{},{})\n".format(entry['StarSystem'], *tuple(entry['StarPos'])))
|
||||
else:
|
||||
sys.stderr.write("Arrived at {}\n".format(entry['StarSystem']))
|
||||
```
|
||||
|
||||
### Getting Commander Data
|
||||
|
||||
This gets called when EDMC has just fetched fresh data from Frontier's servers.
|
||||
This gets called when EDMC has just fetched fresh Cmdr and station data from Frontier's servers.
|
||||
|
||||
```
|
||||
def cmdr_data(data):
|
||||
|
18
README.md
18
README.md
@ -1,10 +1,10 @@
|
||||
Elite: Dangerous Market Connector (EDMC)
|
||||
========
|
||||
|
||||
This app downloads your data and station data from the game [Elite: Dangerous](https://www.elitedangerous.com/) and, at your choice, either:
|
||||
This app downloads your Cmdr's data, system, scan and station data from the game [Elite: Dangerous](https://www.elitedangerous.com/) and, at your choice, either:
|
||||
|
||||
* sends the station commodity market prices and other station data to the [Elite Dangerous Data Network](http://eddn-gateway.elite-markets.net/) (“EDDN”) from where you and others can use it via online trading and shopping tools such as [eddb](http://eddb.io/), [Elite Trade Net](http://etn.io/), [Inara](http://inara.cz), [ED-TD](http://ed-td.space/), [Thrudd's Trading Tools](http://www.elitetradingtool.co.uk/), [Roguey's](http://roguey.co.uk/elite-dangerous/), etc.
|
||||
* saves the station market prices to files on your computer that you can load into trading tools such as [Slopey's BPC Market Tool](https://forums.frontier.co.uk/showthread.php?t=76081), [Trade Dangerous](https://bitbucket.org/kfsone/tradedangerous/wiki/Home), [Thrudd's Trading Tools](http://www.elitetradingtool.co.uk/), [Inara](http://inara.cz), [mEDI's Elite Tools](https://github.com/mEDI-S/mEDI_s-Elite-Tools), etc.
|
||||
* sends the station commodity market prices, other station data and system and scan data to the [Elite Dangerous Data Network](http://eddn-gateway.elite-markets.net/) (“EDDN”) from where you and others can use it via online trading, prospecting and shopping tools such as [eddb](http://eddb.io/), [Elite Trade Net](http://etn.io/), [Inara](http://inara.cz), [ED-TD](http://ed-td.space/), [Thrudd's Trading Tools](http://www.elitetradingtool.co.uk/), [Roguey's](http://roguey.co.uk/elite-dangerous/), etc.
|
||||
* saves the station commodity market prices to files on your computer that you can load into trading tools such as [Slopey's BPC Market Tool](https://forums.frontier.co.uk/showthread.php?t=76081), [Trade Dangerous](https://bitbucket.org/kfsone/tradedangerous/wiki/Home), [Thrudd's Trading Tools](http://www.elitetradingtool.co.uk/), [Inara](http://inara.cz), [mEDI's Elite Tools](https://github.com/mEDI-S/mEDI_s-Elite-Tools), etc.
|
||||
* saves a record of your ship loadout to files on your computer that you can load into outfitting tools such as [E:D Shipyard](http://www.edshipyard.com), [Coriolis](http://coriolis.io) or [Elite Trade Net](http://etn.io/).
|
||||
* sends your flight log to [Elite: Dangerous Star Map](http://www.edsm.net/).
|
||||
|
||||
@ -46,7 +46,7 @@ Setup
|
||||
The first time that you run the app you are prompted for your username and password. This is the same username and password
|
||||
combination that you use to log into the Elite: Dangerous launcher, and is required so that the Frontier servers can send the app *your* data and the data for the station that *you* are docked at.
|
||||
|
||||
You can also choose here what data to save (refer to the next section for details), whether to “Update” automatically on docking and/or with a hotkey, and whether to attach your Cmdr name or a [pseudo-anonymized](http://en.wikipedia.org/wiki/Pseudonymity) ID to the data.
|
||||
You can also choose here what data to save (refer to the next section for details), whether to “Update” Cmdr and station data automatically on docking and/or with a hotkey, and whether to attach your Cmdr name or a [pseudo-anonymized](http://en.wikipedia.org/wiki/Pseudonymity) ID to the data.
|
||||
|
||||
You will be prompted to authenticate with a “verification code”, which you will shortly receive by email from Frontier.
|
||||
Note that each “verification code” is one-time only - if you enter the code incorrectly or quit the app before
|
||||
@ -59,8 +59,7 @@ option EDMarketConnector → Preferences (Mac) or File → Settings (Windows) an
|
||||
|
||||
This app can save a variety of data in a variety of formats:
|
||||
|
||||
* Station data
|
||||
* Elite Dangerous Data Network - sends station commodity market, outfitting and shipyard data to “[EDDN](http://eddn-gateway.elite-markets.net/)” from where you and others can use it via online trading tools such as [eddb](http://eddb.io/), [Elite Trade Net](http://etn.io/), [Inara](http://inara.cz), [ED-TD](http://ed-td.space/), [Thrudd's Trading Tools](http://www.elitetradingtool.co.uk/), [Roguey's](http://roguey.co.uk/elite-dangerous/), etc.
|
||||
* Market data
|
||||
* Slopey's BPC format file - saves commodity market data as files that you can load into [Slopey's BPC Market Tool](https://forums.frontier.co.uk/showthread.php?t=76081).
|
||||
* Trade Dangerous format file - saves commodity market data as files that you can load into [Trade Dangerous](https://bitbucket.org/kfsone/tradedangerous/wiki/Home).
|
||||
* CSV format file - saves commodity market data as files that you can upload to [Thrudd's Trading Tools](http://www.elitetradingtool.co.uk/), [Inara](http://inara.cz) or [mEDI's Elite Tools](https://github.com/mEDI-S/mEDI_s-Elite-Tools).
|
||||
@ -72,6 +71,13 @@ By default these files will be placed in your Documents folder. Since this app w
|
||||
|
||||
Some options work by reading the Elite: Dangerous game's log files. Normally this app will find the log files but if you find some options greyed-out then adjust the “E:D log file location” setting described [below](#doesnt-track-systems-visited).
|
||||
|
||||
### EDDN
|
||||
|
||||
* Station data
|
||||
* Sends station commodity market, outfitting and shipyard data to “[EDDN](http://eddn-gateway.elite-markets.net/)” from where you and others can use it via online trading tools such as [eddb](http://eddb.io/), [Elite Trade Net](http://etn.io/), [Inara](http://inara.cz), [ED-TD](http://ed-td.space/), [Thrudd's Trading Tools](http://www.elitetradingtool.co.uk/), [Roguey's](http://roguey.co.uk/elite-dangerous/), etc.
|
||||
* System and scan data
|
||||
* Sends general system information and the results of your detailed planet scans to “[EDDN](http://eddn-gateway.elite-markets.net/)” from where you and others can use it via online prospecting tools such as [eddb](http://eddb.io/), [Inara](http://inara.cz), etc.
|
||||
|
||||
### EDSM
|
||||
|
||||
You can send a record of your location to [Elite: Dangerous Star Map](http://www.edsm.net/) where you can view your flight log under My account → Exploration Logs and optionally add private comments about a system. You will need to register for an account and then follow the “[Elite Dangerous Star Map credentials](http://www.edsm.net/settings/api)” link to obtain your API key.
|
||||
|
@ -86,6 +86,7 @@ class Config:
|
||||
OUT_SYS_EDSM = 256
|
||||
# OUT_SYS_AUTO = 512 # Now always automatic
|
||||
OUT_MKT_MANUAL = 1024
|
||||
OUT_SYS_EDDN = 2048
|
||||
|
||||
if platform=='darwin':
|
||||
|
||||
|
9
eddn.py
9
eddn.py
@ -32,7 +32,8 @@ class _EDDN:
|
||||
'softwareVersion' : appversion,
|
||||
'uploaderID' : config.getint('anonymous') and hashlib.md5(cmdr.encode('utf-8')).hexdigest() or cmdr.encode('utf-8'),
|
||||
}
|
||||
msg['message']['timestamp'] = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(config.getint('querytime') or int(time.time())))
|
||||
if not msg['message'].get('timestamp'): # already present in journal messages
|
||||
msg['message']['timestamp'] = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(config.getint('querytime') or int(time.time())))
|
||||
|
||||
r = self.session.post(self.UPLOAD, data=json.dumps(msg), timeout=timeout)
|
||||
if __debug__ and r.status_code != requests.codes.ok:
|
||||
@ -94,5 +95,11 @@ class _EDDN:
|
||||
}
|
||||
})
|
||||
|
||||
def export_journal_entry(self, cmdr, is_beta, entry):
|
||||
self.send(cmdr, {
|
||||
'$schemaRef' : 'http://schemas.elite-markets.net/eddn/journal/1' + (is_beta and '/test' or ''),
|
||||
'message' : entry
|
||||
})
|
||||
|
||||
# singleton
|
||||
eddn = _EDDN()
|
||||
|
140
monitor.py
140
monitor.py
@ -1,4 +1,6 @@
|
||||
import atexit
|
||||
from collections import OrderedDict
|
||||
import json
|
||||
import re
|
||||
import threading
|
||||
from os import listdir, pardir, rename, unlink, SEEK_SET, SEEK_CUR, SEEK_END
|
||||
@ -6,8 +8,7 @@ from os.path import basename, exists, isdir, isfile, join
|
||||
from platform import machine
|
||||
import sys
|
||||
from sys import platform
|
||||
from time import strptime, localtime, mktime, sleep, time
|
||||
from datetime import datetime
|
||||
from time import sleep
|
||||
|
||||
if __debug__:
|
||||
from traceback import print_exc
|
||||
@ -68,11 +69,18 @@ class EDLogs(FileSystemEventHandler):
|
||||
self.currentdir = None # The actual logdir that we're monitoring
|
||||
self.logfile = None
|
||||
self.observer = None
|
||||
self.observed = None
|
||||
self.observed = None # a watchdog ObservedWatch, or None if polling
|
||||
self.thread = None
|
||||
self.callbacks = { 'Jump': None, 'Dock': None }
|
||||
self.last_event = None # for communicating the Jump event
|
||||
self.event_queue = [] # For communicating journal entries back to main thread
|
||||
|
||||
# Context for journal handling
|
||||
self.version = None
|
||||
self.is_beta = False
|
||||
self.mode = None
|
||||
self.cmdr = None
|
||||
self.system = None
|
||||
self.station = None
|
||||
self.coordinates = None
|
||||
|
||||
def set_callback(self, name, callback):
|
||||
if name in self.callbacks:
|
||||
@ -89,9 +97,6 @@ class EDLogs(FileSystemEventHandler):
|
||||
self.stop()
|
||||
self.currentdir = logdir
|
||||
|
||||
self.root.bind_all('<<MonitorJump>>', self.jump) # user-generated
|
||||
self.root.bind_all('<<MonitorDock>>', self.dock) # user-generated
|
||||
|
||||
# Set up a watchog observer. This is low overhead so is left running irrespective of whether monitoring is desired.
|
||||
# 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
|
||||
@ -108,17 +113,17 @@ class EDLogs(FileSystemEventHandler):
|
||||
|
||||
# Latest pre-existing logfile - e.g. if E:D is already running. Assumes logs sort alphabetically.
|
||||
try:
|
||||
logfiles = sorted([x for x in listdir(logdir) if x.startswith('netLog.')])
|
||||
self.logfile = logfiles and join(logdir, logfiles[-1]) or None
|
||||
logfiles = sorted([x for x in listdir(self.currentdir) if x.startswith('Journal.')])
|
||||
self.logfile = logfiles and join(self.currentdir, logfiles[-1]) or None
|
||||
except:
|
||||
self.logfile = None
|
||||
|
||||
if __debug__:
|
||||
print '%s "%s"' % (polling and 'Polling' or 'Monitoring', logdir)
|
||||
print '%s "%s"' % (polling and 'Polling' or 'Monitoring', self.currentdir)
|
||||
print 'Start logfile "%s"' % self.logfile
|
||||
|
||||
if not self.running():
|
||||
self.thread = threading.Thread(target = self.worker, name = 'netLog worker')
|
||||
self.thread = threading.Thread(target = self.worker, name = 'Journal worker')
|
||||
self.thread.daemon = True
|
||||
self.thread.start()
|
||||
|
||||
@ -128,18 +133,19 @@ class EDLogs(FileSystemEventHandler):
|
||||
if __debug__:
|
||||
print 'Stopping monitoring'
|
||||
self.currentdir = None
|
||||
self.version = self.mode = self.cmdr = self.system = self.station = self.coordinates = None
|
||||
self.is_beta = False
|
||||
if self.observed:
|
||||
self.observed = None
|
||||
self.observer.unschedule_all()
|
||||
self.thread = None # Orphan the worker thread - will terminate at next poll
|
||||
self.last_event = None
|
||||
|
||||
def running(self):
|
||||
return self.thread and self.thread.is_alive()
|
||||
|
||||
def on_created(self, event):
|
||||
# watchdog callback, e.g. client (re)started.
|
||||
if not event.is_directory and basename(event.src_path).startswith('netLog.'):
|
||||
if not event.is_directory and basename(event.src_path).startswith('Journal.'):
|
||||
self.logfile = event.src_path
|
||||
|
||||
def worker(self):
|
||||
@ -147,30 +153,16 @@ class EDLogs(FileSystemEventHandler):
|
||||
# event_generate() is the only safe way to poke the main thread from this thread:
|
||||
# https://mail.python.org/pipermail/tkinter-discuss/2013-November/003522.html
|
||||
|
||||
# e.g.:
|
||||
# "{18:00:41} System:"Shinrarta Dezhra" StarPos:(55.719,17.594,27.156)ly NormalFlight\r\n"
|
||||
# or with verboseLogging:
|
||||
# "{17:20:18} System:"Shinrarta Dezhra" StarPos:(55.719,17.594,27.156)ly Body:69 RelPos:(0.334918,1.20754,1.23625)km NormalFlight\r\n"
|
||||
# or:
|
||||
# "... Supercruise\r\n"
|
||||
# Note that system name may contain parantheses, e.g. "Pipe (stem) Sector PI-T c3-5".
|
||||
regexp = re.compile(r'\{(.+)\} System:"(.+)" StarPos:\((.+),(.+),(.+)\)ly.* (\S+)') # (localtime, system, x, y, z, context)
|
||||
|
||||
# e.g.:
|
||||
# "{14:42:11} GetSafeUniversalAddress Station Count 1 moved 0 Docked Not Landed\r\n"
|
||||
# or:
|
||||
# "... Undocked Landed\r\n"
|
||||
# Don't use the simpler "Commander Put ..." message since its more likely to be delayed.
|
||||
dockre = re.compile(r'\{(.+)\} GetSafeUniversalAddress Station Count \d+ moved \d+ (\S+) ([^\r\n]+)') # (localtime, docked_status, landed_status)
|
||||
|
||||
docked = False # Whether we're docked
|
||||
updated = False # Whether we've sent an update since we docked
|
||||
|
||||
# Seek to the end of the latest log file
|
||||
logfile = self.logfile
|
||||
if logfile:
|
||||
loghandle = open(logfile, 'r')
|
||||
loghandle.seek(0, SEEK_END) # seek to EOF
|
||||
for line in loghandle:
|
||||
try:
|
||||
self.parse_entry(line) # Some events are of interest even in the past
|
||||
except:
|
||||
if __debug__:
|
||||
print 'Invalid journal entry "%s"' % repr(line)
|
||||
else:
|
||||
loghandle = None
|
||||
|
||||
@ -179,19 +171,13 @@ class EDLogs(FileSystemEventHandler):
|
||||
|
||||
while True:
|
||||
|
||||
if docked and not updated and not config.getint('output') & config.OUT_MKT_MANUAL:
|
||||
self.root.event_generate('<<MonitorDock>>', when="tail")
|
||||
updated = True
|
||||
if __debug__:
|
||||
print "%s :\t%s %s" % ('Updated', docked and " docked" or "!docked", updated and " updated" or "!updated")
|
||||
|
||||
# Check whether new log file started, e.g. client (re)started.
|
||||
if emitter and emitter.is_alive():
|
||||
newlogfile = self.logfile # updated by on_created watchdog callback
|
||||
else:
|
||||
# Poll
|
||||
try:
|
||||
logfiles = sorted([x for x in listdir(self.currentdir) if x.startswith('netLog.')])
|
||||
logfiles = sorted([x for x in listdir(self.currentdir) if x.startswith('Journal.')])
|
||||
newlogfile = logfiles and join(self.currentdir, logfiles[-1]) or None
|
||||
except:
|
||||
if __debug__: print_exc()
|
||||
@ -207,37 +193,11 @@ class EDLogs(FileSystemEventHandler):
|
||||
print 'New logfile "%s"' % logfile
|
||||
|
||||
if logfile:
|
||||
system = visited = coordinates = None
|
||||
loghandle.seek(0, SEEK_CUR) # reset EOF flag
|
||||
|
||||
for line in loghandle:
|
||||
match = regexp.match(line)
|
||||
if match:
|
||||
(visited, system, x, y, z, context) = match.groups()
|
||||
if system == 'ProvingGround':
|
||||
system = 'CQC'
|
||||
coordinates = (float(x), float(y), float(z))
|
||||
else:
|
||||
match = dockre.match(line)
|
||||
if match:
|
||||
if match.group(2) == 'Undocked':
|
||||
docked = updated = False
|
||||
elif match.group(2) == 'Docked':
|
||||
docked = True
|
||||
# do nothing now in case the API server is lagging, but update on next poll
|
||||
if __debug__:
|
||||
print "%s :\t%s %s" % (match.group(2), docked and " docked" or "!docked", updated and " updated" or "!updated")
|
||||
|
||||
if system and not docked and config.getint('output'):
|
||||
# Convert local time string to UTC date and time
|
||||
visited_struct = strptime(visited, '%H:%M:%S')
|
||||
now = localtime()
|
||||
if now.tm_hour == 0 and visited_struct.tm_hour == 23:
|
||||
# Crossed midnight between timestamp and poll
|
||||
now = localtime(time()-12*60*60) # yesterday
|
||||
time_struct = datetime(now.tm_year, now.tm_mon, now.tm_mday, visited_struct.tm_hour, visited_struct.tm_min, visited_struct.tm_sec).timetuple() # still local time
|
||||
self.last_event = (mktime(time_struct), system, coordinates)
|
||||
self.root.event_generate('<<MonitorJump>>', when="tail")
|
||||
self.event_queue.append(line)
|
||||
if self.event_queue:
|
||||
self.root.event_generate('<<JournalEvent>>', when="tail")
|
||||
|
||||
sleep(self._POLL)
|
||||
|
||||
@ -245,15 +205,39 @@ class EDLogs(FileSystemEventHandler):
|
||||
if threading.current_thread() != self.thread:
|
||||
return # Terminate
|
||||
|
||||
def jump(self, event):
|
||||
# Called from Tkinter's main loop
|
||||
if self.callbacks['Jump'] and self.last_event:
|
||||
self.callbacks['Jump'](event, *self.last_event)
|
||||
def parse_entry(self, line):
|
||||
try:
|
||||
entry = json.loads(line, object_pairs_hook=OrderedDict) # Preserve property order because why not?
|
||||
entry['timestamp'] # we expect this to exist
|
||||
if entry['event'] == 'Fileheader': # XXX or 'fileheader' ?
|
||||
self.version = entry['gameversion']
|
||||
self.is_beta = 'beta' in entry['gameversion'].lower()
|
||||
elif entry['event'] == 'LoadGame':
|
||||
self.cmdr = entry['Commander']
|
||||
self.mode = entry['GameMode']
|
||||
elif entry['event'] == 'NewCommander':
|
||||
self.cmdr = entry['Name']
|
||||
elif entry['event'] in ['Undocked']:
|
||||
self.station = None
|
||||
self.coordinates = None
|
||||
elif entry['event'] in ['Location', 'FSDJump', 'Docked']:
|
||||
if 'StarPos' in entry:
|
||||
self.coordinates = tuple(entry['StarPos'])
|
||||
elif self.system != entry['StarSystem']:
|
||||
self.coordinates = None # Docked event doesn't include coordinates
|
||||
self.system = entry['StarSystem'] == 'ProvingGround' and 'CQC' or entry['StarSystem']
|
||||
self.station = entry.get('StationName') # May be None
|
||||
return entry
|
||||
except:
|
||||
if __debug__:
|
||||
print 'Invalid journal entry "%s"' % repr(line)
|
||||
return { 'event': None }
|
||||
|
||||
def dock(self, event):
|
||||
# Called from Tkinter's main loop
|
||||
if self.callbacks['Dock']:
|
||||
self.callbacks['Dock']()
|
||||
def get_entry(self):
|
||||
if not self.event_queue:
|
||||
return None
|
||||
else:
|
||||
return self.parse_entry(self.event_queue.pop(0))
|
||||
|
||||
def is_valid_logdir(self, path):
|
||||
return self._is_valid_logdir(path)
|
||||
|
21
plug.py
21
plug.py
@ -87,12 +87,33 @@ def get_plugin_pref(plugname, parent):
|
||||
return None
|
||||
|
||||
|
||||
def notify_journal_entry(cmdr, system, station, entry):
|
||||
"""
|
||||
Send a journal entry to each plugin.
|
||||
:param cmdr: The Cmdr name, or None if not yet known
|
||||
:param system: The current system, or None if not yet known
|
||||
:param station: The current station, or None if not docked or not yet known
|
||||
:param entry: The journal entry as a dictionary
|
||||
:return:
|
||||
"""
|
||||
for plugname in PLUGINS:
|
||||
journal_entry = _get_plugin_func(plugname, "journal_entry")
|
||||
if journal_entry:
|
||||
try:
|
||||
# Pass a copy of the journal entry in case the callee modifies it
|
||||
journal_entry(cmdr, system, station, dict(entry))
|
||||
except Exception as plugerr:
|
||||
print plugerr
|
||||
|
||||
|
||||
def notify_system_changed(timestamp, system, coordinates):
|
||||
"""
|
||||
Send notification data to each plugin when we arrive at a new system.
|
||||
:param timestamp:
|
||||
:param system:
|
||||
:return:
|
||||
deprecated:: 2.2
|
||||
Use :func:`journal_entry` with the 'FSDJump' event.
|
||||
"""
|
||||
for plugname in PLUGINS:
|
||||
system_changed = _get_plugin_func(plugname, "system_changed")
|
||||
|
@ -45,18 +45,21 @@ def plugin_app(parent):
|
||||
return plugin_app.status
|
||||
|
||||
|
||||
def system_changed(timestamp, system, coordinates):
|
||||
def journal_entry(cmdr, system, station, entry):
|
||||
"""
|
||||
Arrived in a new System
|
||||
:param timestamp: when we arrived
|
||||
:param system: the name of the system
|
||||
:param coordinates: tuple of (x,y,z) ly relative to Sol, or None if unknown
|
||||
E:D client made a journal entry
|
||||
:param cmdr: The Cmdr name, or None if not yet known
|
||||
:param system: The current system, or None if not yet known
|
||||
:param station: The current station, or None if not docked or not yet known
|
||||
:param entry: The journal entry as a dictionary
|
||||
:return:
|
||||
"""
|
||||
if coordinates:
|
||||
sys.stderr.write("Arrived at {} ({},{},{})\n".format(system, *coordinates))
|
||||
else:
|
||||
sys.stderr.write("Arrived at {}\n".format(system))
|
||||
if entry['event'] == 'FSDJump':
|
||||
# We arrived at a new system!
|
||||
if 'StarPos' in entry:
|
||||
sys.stderr.write("Arrived at {} ({},{},{})\n".format(entry['StarSystem'], *tuple(entry['StarPos'])))
|
||||
else:
|
||||
sys.stderr.write("Arrived at {}\n".format(entry['StarSystem']))
|
||||
|
||||
|
||||
def cmdr_data(data):
|
||||
|
31
prefs.py
31
prefs.py
@ -113,11 +113,9 @@ class PreferencesDialog(tk.Toplevel):
|
||||
outframe = nb.Frame(notebook)
|
||||
outframe.columnconfigure(0, weight=1)
|
||||
|
||||
output = config.getint('output') or (config.OUT_MKT_EDDN | config.OUT_SHIP_EDS) # default settings
|
||||
output = config.getint('output') or (config.OUT_MKT_EDDN | config.OUT_SYS_EDDN | config.OUT_SHIP_EDS) # default settings
|
||||
|
||||
nb.Label(outframe, text=_('Please choose what data to save')).grid(columnspan=2, padx=PADX, sticky=tk.W)
|
||||
self.out_eddn= tk.IntVar(value = (output & config.OUT_MKT_EDDN) and 1)
|
||||
nb.Checkbutton(outframe, text=_('Send station data to the Elite Dangerous Data Network'), variable=self.out_eddn, command=self.outvarchanged).grid(columnspan=2, padx=BUTTONX, sticky=tk.W)
|
||||
self.out_csv = tk.IntVar(value = (output & config.OUT_MKT_CSV ) and 1)
|
||||
nb.Checkbutton(outframe, text=_('Market data in CSV format file'), variable=self.out_csv, command=self.outvarchanged).grid(columnspan=2, padx=BUTTONX, sticky=tk.W)
|
||||
self.out_bpc = tk.IntVar(value = (output & config.OUT_MKT_BPC ) and 1)
|
||||
@ -149,13 +147,27 @@ class PreferencesDialog(tk.Toplevel):
|
||||
notebook.add(outframe, text=_('Output')) # Tab heading in settings
|
||||
|
||||
|
||||
eddnframe = nb.Frame(notebook)
|
||||
|
||||
HyperlinkLabel(eddnframe, text='Elite Dangerous Data Network', background=nb.Label().cget('background'), url='https://github.com/jamesremuscat/EDDN/wiki', underline=True).grid(padx=PADX, sticky=tk.W) # Don't translate
|
||||
self.eddn_station= tk.IntVar(value = (output & config.OUT_MKT_EDDN) and 1)
|
||||
nb.Checkbutton(eddnframe, text=_('Send station data to the Elite Dangerous Data Network'), variable=self.eddn_station, command=self.outvarchanged).grid(padx=BUTTONX, pady=(5,0), sticky=tk.W) # Output setting
|
||||
self.eddn_auto_button = nb.Checkbutton(eddnframe, text=_('Automatically update on docking'), variable=self.out_auto, command=self.outvarchanged) # Output setting
|
||||
self.eddn_auto_button.grid(padx=BUTTONX, sticky=tk.W)
|
||||
self.eddn_system = tk.IntVar(value = (output & config.OUT_SYS_EDDN) and 1)
|
||||
self.eddn_system_button = nb.Checkbutton(eddnframe, text=_('Send system and scan data to the Elite Dangerous Data Network'), variable=self.eddn_system, command=self.outvarchanged) # Output setting new in E:D 2.2
|
||||
self.eddn_system_button.grid(padx=BUTTONX, pady=(5,0), sticky=tk.W)
|
||||
|
||||
notebook.add(eddnframe, text='EDDN') # Not translated
|
||||
|
||||
|
||||
edsmframe = nb.Frame(notebook)
|
||||
edsmframe.columnconfigure(1, weight=1)
|
||||
|
||||
HyperlinkLabel(edsmframe, text='Elite Dangerous Star Map', background=nb.Label().cget('background'), url='https://www.edsm.net/', underline=True).grid(columnspan=2, padx=PADX, sticky=tk.W) # Don't translate
|
||||
self.edsm_log = tk.IntVar(value = (output & config.OUT_SYS_EDSM) and 1)
|
||||
self.edsm_log_button = nb.Checkbutton(edsmframe, text=_('Send flight log to Elite Dangerous Star Map'), variable=self.edsm_log, command=self.outvarchanged)
|
||||
self.edsm_log_button.grid(columnspan=2, padx=BUTTONX, sticky=tk.W)
|
||||
self.edsm_log_button.grid(columnspan=2, padx=BUTTONX, pady=(5,0), sticky=tk.W)
|
||||
|
||||
nb.Label(edsmframe).grid(sticky=tk.W) # big spacer
|
||||
self.edsm_label = HyperlinkLabel(edsmframe, text=_('Elite Dangerous Star Map credentials'), background=nb.Label().cget('background'), url='https://www.edsm.net/settings/api', underline=True) # Section heading in settings
|
||||
@ -196,7 +208,8 @@ class PreferencesDialog(tk.Toplevel):
|
||||
_('Browse...')), # Folder selection button on Windows
|
||||
command = lambda:self.filebrowse(_('E:D log file location'), self.logdir))
|
||||
self.logbutton.grid(row=10, column=2, padx=PADX, sticky=tk.EW)
|
||||
nb.Button(configframe, text=_('Default'), command=self.logdir_reset, state = monitor.logdir and tk.NORMAL or tk.DISABLED).grid(column=2, padx=PADX, pady=(5,0), sticky=tk.EW) # Appearance theme and language setting
|
||||
if monitor.logdir:
|
||||
nb.Button(configframe, text=_('Default'), command=self.logdir_reset, state = monitor.logdir and tk.NORMAL or tk.DISABLED).grid(column=2, padx=PADX, pady=(5,0), sticky=tk.EW) # Appearance theme and language setting
|
||||
|
||||
if platform == 'win32':
|
||||
ttk.Separator(configframe, orient=tk.HORIZONTAL).grid(columnspan=3, padx=PADX, pady=PADY*8, sticky=tk.EW)
|
||||
@ -298,11 +311,14 @@ class PreferencesDialog(tk.Toplevel):
|
||||
logvalid = monitor.is_valid_logdir(logdir)
|
||||
|
||||
local = self.out_bpc.get() or self.out_td.get() or self.out_csv.get() or self.out_ship_eds.get() or self.out_ship_coriolis.get()
|
||||
self.out_auto_button['state'] = (local or self.out_eddn.get()) and logvalid and tk.NORMAL or tk.DISABLED
|
||||
self.out_auto_button['state'] = local and logvalid and tk.NORMAL or tk.DISABLED
|
||||
self.outdir_label['state'] = local and tk.NORMAL or tk.DISABLED
|
||||
self.outbutton['state'] = local and tk.NORMAL or tk.DISABLED
|
||||
self.outdir['state'] = local and 'readonly' or tk.DISABLED
|
||||
|
||||
self.eddn_auto_button['state'] = self.eddn_station.get() and logvalid and tk.NORMAL or tk.DISABLED
|
||||
self.eddn_system_button['state']= logvalid and tk.NORMAL or tk.DISABLED
|
||||
|
||||
self.edsm_log_button['state'] = logvalid and tk.NORMAL or tk.DISABLED
|
||||
edsm_state = logvalid and self.edsm_log.get() and tk.NORMAL or tk.DISABLED
|
||||
self.edsm_label['state'] = edsm_state
|
||||
@ -425,13 +441,14 @@ class PreferencesDialog(tk.Toplevel):
|
||||
config.set('password', self.password.get().strip())
|
||||
|
||||
config.set('output',
|
||||
(self.out_eddn.get() and config.OUT_MKT_EDDN) +
|
||||
(self.out_bpc.get() and config.OUT_MKT_BPC) +
|
||||
(self.out_td.get() and config.OUT_MKT_TD) +
|
||||
(self.out_csv.get() and config.OUT_MKT_CSV) +
|
||||
(config.OUT_MKT_MANUAL if not self.out_auto.get() else 0) +
|
||||
(self.out_ship_eds.get() and config.OUT_SHIP_EDS) +
|
||||
(self.out_ship_coriolis.get() and config.OUT_SHIP_CORIOLIS) +
|
||||
(self.eddn_station.get() and config.OUT_MKT_EDDN) +
|
||||
(self.eddn_system.get() and config.OUT_SYS_EDDN) +
|
||||
(self.edsm_log.get() and config.OUT_SYS_EDSM))
|
||||
config.set('outdir', self.outdir.get().startswith('~') and join(config.home, self.outdir.get()[2:]) or self.outdir.get())
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user