1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-04-18 09:57:40 +03:00

Add support for edproxy on same subnet.

Fixes #82.
This commit is contained in:
Jonathan Harris 2016-01-25 18:13:37 +00:00
parent 5d25155e0d
commit 06006432a3
4 changed files with 196 additions and 25 deletions

View File

@ -40,6 +40,7 @@ import eddb
import stats import stats
import prefs import prefs
import plug import plug
from edproxy import edproxy
from hotkey import hotkeymgr from hotkey import hotkeymgr
from monitor import monitor from monitor import monitor
@ -195,10 +196,12 @@ class AppWindow:
hotkeymgr.register(self.w, config.getint('hotkey_code'), config.getint('hotkey_mods')) hotkeymgr.register(self.w, config.getint('hotkey_code'), config.getint('hotkey_mods'))
# Install log monitoring # Install log monitoring
self.w.bind_all('<<Jump>>', self.system_change) # user-generated monitor.set_callback(self.system_change)
if (config.getint('output') & config.OUT_LOG_AUTO) and (config.getint('output') & (config.OUT_LOG_AUTO|config.OUT_LOG_EDSM)): edproxy.set_callback(self.system_change)
if (config.getint('output') & config.OUT_LOG_AUTO) and (config.getint('output') & (config.OUT_LOG_FILE|config.OUT_LOG_EDSM)):
monitor.enable_logging() monitor.enable_logging()
monitor.start(self.w) monitor.start(self.w)
edproxy.start(self.w)
# First run # First run
if not config.get('username') or not config.get('password'): if not config.get('username') or not config.get('password'):
@ -437,13 +440,7 @@ class AppWindow:
except: except:
pass pass
def system_change(self, event): def system_change(self, timestamp, system):
if not monitor.last_event:
if __debug__: print 'spurious system_change', event # eh?
return
timestamp, system = monitor.last_event # would like to use event user_data to carry this, but not accessible in Tkinter
if self.system['text'] != system: if self.system['text'] != system:
self.system['text'] = system self.system['text'] = system

150
edproxy.py Normal file
View File

@ -0,0 +1,150 @@
#
# Elite: Dangerous Netlog Proxy Server client - https://bitbucket.org/westokyo/edproxy/
#
import json
import socket
import struct
import sys
import threading
from time import time, strptime, mktime
from datetime import datetime
if __debug__:
from traceback import print_exc
class _EDProxy:
DISCOVERY_ADDR = '239.45.99.98'
DISCOVERY_PORT = 45551
DISCOVERY_QUERY = 'Query'
DISCOVERY_ANNOUNCE = 'Announce'
SERVICE_NAME = 'edproxy'
SERVICE_HEARTBEAT = 60 # [s]
SERVICE_TIMEOUT = 90 # [s]
MESSAGE_MAX = 1024 # https://bitbucket.org/westokyo/edproxy/src/master/ednet.py?fileviewer=file-view-default#ednet.py-166
MESSAGE_SYSTEM = 'System'
def __init__(self):
self.lock = threading.Lock()
self.addr = None
self.port = None
thread = threading.Thread(target = self._listener)
thread.daemon = True
thread.start()
self.callback = None
self.last_event = None # for communicating the Jump event
# start
self.discover_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.discover()
def set_callback(self, callback):
self.callback = callback
def start(self, root):
self.root = root
self.root.bind_all('<<ProxyJump>>', self.jump) # user-generated
def stop(self):
# Still listening, but stop callbacks
self.root.unbind_all('<<ProxyJump>>')
def status(self):
self.lock.acquire()
if self.addr and self.port:
status = '%s:%d' % (self.addr, self.port)
self.lock.release()
return status
else:
self.lock.release()
self.discover() # Kick off discovery
return None
def jump(self, event):
# Called from Tkinter's main loop
if self.callback and self.last_event:
self.callback(*self.last_event)
def close():
self.discover_sock.shutdown()
self.discover_sock = None
# Send a query. _listener should get the response.
def discover(self):
self.discover_sock.sendto(json.dumps({
'type': self.DISCOVERY_QUERY,
'name': self.SERVICE_NAME,
}), (self.DISCOVERY_ADDR, self.DISCOVERY_PORT))
def _listener(self):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
if sys.platform == 'win32':
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
else:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
s.bind(('', self.DISCOVERY_PORT))
s.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, struct.pack('4sL', socket.inet_aton(self.DISCOVERY_ADDR), socket.INADDR_ANY))
while True:
try:
(data, addr) = s.recvfrom(self.MESSAGE_MAX)
msg = json.loads(data)
if msg['name'] == self.SERVICE_NAME and msg['type'] == self.DISCOVERY_ANNOUNCE:
# ignore if already connected to a proxy
if not self.addr or not self.port:
thread = threading.Thread(target = self._worker, args = (msg['ipv4'], int(msg['port'])))
thread.daemon = True
thread.start()
except:
if __debug__: print_exc()
def _worker(self, addr, port):
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((addr, port))
s.settimeout(0)
s.sendall(json.dumps({
'Type' : 'Init',
'DateUtc' : datetime.now().isoformat(),
'StartTime' : 'now',
'Register' : [ 'System' ],
}))
except:
if __debug__: print_exc()
return
self.lock.acquire()
self.addr = addr
self.port = port
self.lock.release()
try:
s.settimeout(None) # was self.SERVICE_TIMEOUT, but heartbeat doesn't appear to work so wait indefinitely
while True:
msg = json.loads(s.recv(self.MESSAGE_MAX))
if msg['Type'] == self.MESSAGE_SYSTEM:
if 'DateUtc' in msg:
timestamp = mktime(datetime.strptime(msg['DateUtc'], '%Y-%m-%d %H:%M:%S').utctimetuple())
else:
timestamp = mktime(strptime(msg['Date'], '%Y-%m-%d %H:%M:%S')) # from local time
self.last_event = (timestamp, msg['System'])
self.root.event_generate('<<ProxyJump>>', when="tail")
except:
if __debug__: print_exc()
self.lock.acquire()
self.addr = self.port = None
self.lock.release()
# Kick off discovery for another proxy
self.discover()
# singleton
edproxy = _EDProxy()

View File

@ -81,6 +81,7 @@ class EDLogs(FileSystemEventHandler):
self._restart_required = False self._restart_required = False
self.observer = None self.observer = None
self.thread = None self.thread = None
self.callback = None
self.last_event = None # for communicating the Jump event self.last_event = None # for communicating the Jump event
def enable_logging(self): def enable_logging(self):
@ -143,6 +144,9 @@ class EDLogs(FileSystemEventHandler):
if __debug__: print_exc() if __debug__: print_exc()
return False return False
def set_callback(self, callback):
self.callback = callback
def start(self, root): def start(self, root):
self.root = root self.root = root
if not self.logdir: if not self.logdir:
@ -151,6 +155,8 @@ class EDLogs(FileSystemEventHandler):
if self.running(): if self.running():
return True return True
self.root.bind_all('<<MonitorJump>>', self.jump) # user-generated
# Set up a watchog observer. This is low overhead so is left running irrespective of whether monitoring is desired. # Set up a watchog observer. This is low overhead so is left running irrespective of whether monitoring is desired.
if not self.observer: if not self.observer:
if __debug__: if __debug__:
@ -239,7 +245,7 @@ class EDLogs(FileSystemEventHandler):
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 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
# Tk on Windows doesn't like to be called outside of an event handler, so generate an event # Tk on Windows doesn't like to be called outside of an event handler, so generate an event
self.last_event = (mktime(time_struct), system) self.last_event = (mktime(time_struct), system)
self.root.event_generate('<<Jump>>', when="tail") self.root.event_generate('<<MonitorJump>>', when="tail")
sleep(10) # New system gets posted to log file before hyperspace ends, so don't need to poll too often sleep(10) # New system gets posted to log file before hyperspace ends, so don't need to poll too often
@ -247,6 +253,10 @@ class EDLogs(FileSystemEventHandler):
if threading.current_thread() != self.thread: if threading.current_thread() != self.thread:
return # Terminate return # Terminate
def jump(self, event):
# Called from Tkinter's main loop
if self.callback and self.last_event:
self.callback(*self.last_event)
if platform=='darwin': if platform=='darwin':

View File

@ -11,6 +11,7 @@ from ttkHyperlinkLabel import HyperlinkLabel
import myNotebook as nb import myNotebook as nb
from config import applongname, config from config import applongname, config
from edproxy import edproxy
from hotkey import hotkeymgr from hotkey import hotkeymgr
from monitor import monitor from monitor import monitor
@ -127,10 +128,9 @@ class PreferencesDialog(tk.Toplevel):
nb.Checkbutton(outframe, text=_('Ship loadout in Coriolis format file'), variable=self.out_ship_coriolis, command=self.outvarchanged).grid(columnspan=2, padx=BUTTONX, sticky=tk.W) nb.Checkbutton(outframe, text=_('Ship loadout in Coriolis format file'), variable=self.out_ship_coriolis, command=self.outvarchanged).grid(columnspan=2, padx=BUTTONX, sticky=tk.W)
self.out_log_file = tk.IntVar(value = (output & config.OUT_LOG_FILE) and 1) self.out_log_file = tk.IntVar(value = (output & config.OUT_LOG_FILE) and 1)
nb.Checkbutton(outframe, text=_('Flight log in CSV format file'), variable=self.out_log_file, command=self.outvarchanged).grid(columnspan=2, padx=BUTTONX, pady=(5,0), sticky=tk.W) nb.Checkbutton(outframe, text=_('Flight log in CSV format file'), variable=self.out_log_file, command=self.outvarchanged).grid(columnspan=2, padx=BUTTONX, pady=(5,0), sticky=tk.W)
self.out_log_auto = tk.IntVar(value = monitor.logdir and (output & config.OUT_LOG_AUTO) and 1 or 0) self.out_log_auto = tk.IntVar(value = output & config.OUT_LOG_AUTO and 1 or 0)
if monitor.logdir: self.out_log_auto_button = nb.Checkbutton(outframe, text=_('Automatically make a log entry on entering a system'), variable=self.out_log_auto, command=self.outvarchanged) # Output setting
self.out_log_auto_button = nb.Checkbutton(outframe, text=_('Automatically make a log entry on entering a system'), variable=self.out_log_auto, command=self.outvarchanged) # Output setting self.out_log_auto_button.grid(columnspan=2, padx=BUTTONX, sticky=tk.W)
self.out_log_auto_button.grid(columnspan=2, padx=BUTTONX, sticky=tk.W)
self.out_log_auto_text = nb.Label(outframe, foreground='firebrick') self.out_log_auto_text = nb.Label(outframe, foreground='firebrick')
self.out_log_auto_text.grid(columnspan=2, padx=(30,0), sticky=tk.W) self.out_log_auto_text.grid(columnspan=2, padx=(30,0), sticky=tk.W)
@ -160,9 +160,8 @@ class PreferencesDialog(tk.Toplevel):
self.edsm_autoopen = tk.BooleanVar(value = config.getint('edsm_autoopen')) self.edsm_autoopen = tk.BooleanVar(value = config.getint('edsm_autoopen'))
self.edsm_autoopen_button = nb.Checkbutton(edsmframe, text=_(u"Automatically open uncharted systems EDSM pages"), variable=self.edsm_autoopen) self.edsm_autoopen_button = nb.Checkbutton(edsmframe, text=_(u"Automatically open uncharted systems EDSM pages"), variable=self.edsm_autoopen)
self.edsm_autoopen_button.grid(columnspan=2, padx=BUTTONX, sticky=tk.W) self.edsm_autoopen_button.grid(columnspan=2, padx=BUTTONX, sticky=tk.W)
if monitor.logdir: self.edsm_log_auto_button = nb.Checkbutton(edsmframe, text=_('Automatically make a log entry on entering a system'), variable=self.out_log_auto, command=self.outvarchanged) # Output setting
self.edsm_log_auto_button = nb.Checkbutton(edsmframe, text=_('Automatically make a log entry on entering a system'), variable=self.out_log_auto, command=self.outvarchanged) # Output setting self.edsm_log_auto_button.grid(columnspan=2, padx=BUTTONX, sticky=tk.W)
self.edsm_log_auto_button.grid(columnspan=2, padx=BUTTONX, sticky=tk.W)
self.edsm_log_auto_text = nb.Label(edsmframe, foreground='firebrick') self.edsm_log_auto_text = nb.Label(edsmframe, foreground='firebrick')
self.edsm_log_auto_text.grid(columnspan=2, padx=(30,0), sticky=tk.W) self.edsm_log_auto_text.grid(columnspan=2, padx=(30,0), sticky=tk.W)
@ -237,7 +236,7 @@ class PreferencesDialog(tk.Toplevel):
self.protocol("WM_DELETE_WINDOW", self._destroy) self.protocol("WM_DELETE_WINDOW", self._destroy)
# Selectively disable buttons depending on output settings # Selectively disable buttons depending on output settings
self.outvarchanged() self.proxypoll()
# disable hotkey for the duration # disable hotkey for the duration
hotkeymgr.unregister() hotkeymgr.unregister()
@ -247,6 +246,12 @@ class PreferencesDialog(tk.Toplevel):
self.grab_set() self.grab_set()
#self.wait_window(self) # causes duplicate events on OSX #self.wait_window(self) # causes duplicate events on OSX
def proxypoll(self):
self.outvarchanged()
self.after(1000, self.proxypoll)
def outvarchanged(self): def outvarchanged(self):
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() or self.out_log_file.get() 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() or self.out_log_file.get()
self.outdir_label['state'] = local and tk.NORMAL or tk.DISABLED self.outdir_label['state'] = local and tk.NORMAL or tk.DISABLED
@ -261,26 +266,33 @@ class PreferencesDialog(tk.Toplevel):
self.edsm_cmdr['state'] = edsm_state self.edsm_cmdr['state'] = edsm_state
self.edsm_apikey['state'] = edsm_state self.edsm_apikey['state'] = edsm_state
if monitor.logdir: proxyaddr = edproxy.status()
self.out_log_auto_text['text'] = ''
self.edsm_log_auto_text['text'] = ''
if monitor.logdir or proxyaddr:
log = self.out_log_file.get() log = self.out_log_file.get()
self.out_log_auto_button['state'] = log and tk.NORMAL or tk.DISABLED self.out_log_auto_button['state'] = log and tk.NORMAL or tk.DISABLED
self.out_log_auto_text['text'] = ''
if log and self.out_log_auto.get(): if log and self.out_log_auto.get():
if not monitor.enable_logging(): if proxyaddr:
self.out_log_auto_text['text'] = _('Connected to edproxy at {ADDR}').format(ADDR = proxyaddr)
elif not monitor.enable_logging():
self.out_log_auto_text['text'] = "Can't enable automatic logging!" # Shouldn't happen - don't translate self.out_log_auto_text['text'] = "Can't enable automatic logging!" # Shouldn't happen - don't translate
elif monitor.restart_required(): elif monitor.restart_required():
self.out_log_auto_text['text'] = _('Re-start Elite: Dangerous to use this feature') # Output settings prompt self.out_log_auto_text['text'] = _('Re-start Elite: Dangerous to use this feature') # Output settings prompt
self.edsm_log_auto_button['state'] = edsm_state self.edsm_log_auto_button['state'] = edsm_state
self.edsm_log_auto_text['text'] = ''
if self.out_log_edsm.get() and self.out_log_auto.get(): if self.out_log_edsm.get() and self.out_log_auto.get():
if not monitor.enable_logging(): if proxyaddr:
self.edsm_log_auto_text['text'] = _('Connected to edproxy at {ADDR}').format(ADDR = proxyaddr)
elif not monitor.enable_logging():
self.edsm_log_auto_text['text'] = "Can't enable automatic logging!" # Shouldn't happen - don't translate self.edsm_log_auto_text['text'] = "Can't enable automatic logging!" # Shouldn't happen - don't translate
elif monitor.restart_required(): elif monitor.restart_required():
self.edsm_log_auto_text['text'] = _('Re-start Elite: Dangerous to use this feature') # Output settings prompt self.edsm_log_auto_text['text'] = _('Re-start Elite: Dangerous to use this feature') # Output settings prompt
else:
self.out_log_auto_button['state'] = tk.DISABLED
self.edsm_log_auto_button['state'] = tk.DISABLED
def outbrowse(self): def outbrowse(self):
if platform != 'win32': if platform != 'win32':
@ -393,11 +405,13 @@ class PreferencesDialog(tk.Toplevel):
def _destroy(self): def _destroy(self):
# Re-enable hotkey and log monitoring before exit # Re-enable hotkey and log monitoring before exit
hotkeymgr.register(self.parent, config.getint('hotkey_code'), config.getint('hotkey_mods')) hotkeymgr.register(self.parent, config.getint('hotkey_code'), config.getint('hotkey_mods'))
if (config.getint('output') & config.OUT_LOG_AUTO) and (config.getint('output') & (config.OUT_LOG_AUTO|config.OUT_LOG_EDSM)): if (config.getint('output') & config.OUT_LOG_AUTO) and (config.getint('output') & (config.OUT_LOG_FILE|config.OUT_LOG_EDSM)):
monitor.enable_logging() monitor.enable_logging()
monitor.start(self.parent) monitor.start(self.parent)
edproxy.start(self.parent)
else: else:
monitor.stop() monitor.stop()
edproxy.stop()
self.destroy() self.destroy()
if platform == 'darwin': if platform == 'darwin':