1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-06-11 04:42:23 +03:00

Throttle EDDN output when sending replay

This commit is contained in:
Jonathan Harris 2017-03-11 17:26:40 +00:00
parent dfa87f40d3
commit d4dddb6239
3 changed files with 75 additions and 39 deletions

View File

@ -43,7 +43,7 @@ import companion
import commodity import commodity
from commodity import COMMODITY_CSV from commodity import COMMODITY_CSV
import td import td
from eddn import eddn import eddn
import edsm import edsm
import coriolis import coriolis
import eddb import eddb
@ -76,6 +76,7 @@ class AppWindow:
self.holdofftime = config.getint('querytime') + companion.holdoff self.holdofftime = config.getint('querytime') + companion.holdoff
self.session = companion.Session() self.session = companion.Session()
self.edsm = edsm.EDSM() self.edsm = edsm.EDSM()
self.eddn = eddn.EDDN(self)
self.w = master self.w = master
self.w.title(applongname) self.w.title(applongname)
@ -304,7 +305,7 @@ class AppWindow:
self.status['text'] = 'Warning: Storing passwords as text' # Shouldn't happen unless no secure storage on Linux self.status['text'] = 'Warning: Storing passwords as text' # Shouldn't happen unless no secure storage on Linux
# Try to obtain exclusive lock on journal cache, even if we don't need it yet # Try to obtain exclusive lock on journal cache, even if we don't need it yet
if not eddn.load(): if not self.eddn.load():
self.status['text'] = 'Error: Is another copy of this app already running?' # Shouldn't happen - don't bother localizing self.status['text'] = 'Error: Is another copy of this app already running?' # Shouldn't happen - don't bother localizing
# callback after the Preferences dialog is applied # callback after the Preferences dialog is applied
@ -496,13 +497,13 @@ class AppWindow:
if not old_status: if not old_status:
self.status['text'] = _('Sending data to EDDN...') self.status['text'] = _('Sending data to EDDN...')
self.w.update_idletasks() self.w.update_idletasks()
eddn.export_commodities(data) self.eddn.export_commodities(data)
eddn.export_outfitting(data) self.eddn.export_outfitting(data)
if has_shipyard and not data['lastStarport'].get('ships'): if has_shipyard and not data['lastStarport'].get('ships'):
# API is flakey about shipyard info - silently retry if missing (<1s is usually sufficient - 5s for margin). # API is flakey about shipyard info - silently retry if missing (<1s is usually sufficient - 5s for margin).
self.w.after(int(SERVER_RETRY * 1000), self.retry_for_shipyard) self.w.after(int(SERVER_RETRY * 1000), self.retry_for_shipyard)
else: else:
eddn.export_shipyard(data) self.eddn.export_shipyard(data)
if not old_status: if not old_status:
self.status['text'] = '' self.status['text'] = ''
@ -578,7 +579,7 @@ class AppWindow:
if __debug__: if __debug__:
print 'Retry for shipyard - ' + (data['commander'].get('docked') and (data['lastStarport'].get('ships') and 'Success' or 'Failure') or 'Undocked!') print 'Retry for shipyard - ' + (data['commander'].get('docked') and (data['lastStarport'].get('ships') and 'Success' or 'Failure') or 'Undocked!')
if data['commander'].get('docked'): # might have undocked while we were waiting for retry in which case station data is unreliable if data['commander'].get('docked'): # might have undocked while we were waiting for retry in which case station data is unreliable
eddn.export_shipyard(data) self.eddn.export_shipyard(data)
except: except:
pass pass
@ -704,10 +705,7 @@ class AppWindow:
if 'StarPos' not in entry: if 'StarPos' not in entry:
entry['StarPos'] = list(monitor.coordinates) entry['StarPos'] = list(monitor.coordinates)
self.status['text'] = _('Sending data to EDDN...') self.eddn.export_journal_entry(monitor.cmdr, monitor.is_beta, entry)
self.w.update_idletasks()
eddn.export_journal_entry(monitor.cmdr, monitor.is_beta, entry)
self.status['text'] = ''
elif (config.getint('output') & config.OUT_MKT_EDDN and monitor.cmdr and elif (config.getint('output') & config.OUT_MKT_EDDN and monitor.cmdr and
entry['event'] == 'MarketSell' and entry.get('BlackMarket')): entry['event'] == 'MarketSell' and entry.get('BlackMarket')):
@ -723,7 +721,7 @@ class AppWindow:
self.status['text'] = _('Sending data to EDDN...') self.status['text'] = _('Sending data to EDDN...')
self.w.update_idletasks() self.w.update_idletasks()
eddn.export_blackmarket(monitor.cmdr, monitor.is_beta, msg) self.eddn.export_blackmarket(monitor.cmdr, monitor.is_beta, msg)
self.status['text'] = '' self.status['text'] = ''
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
@ -756,7 +754,7 @@ class AppWindow:
try: try:
data = self.session.query() data = self.session.query()
except companion.VerificationRequired: except companion.VerificationRequired:
return prefs.AuthenticationDialog(self.parent, partial(self.verify, self.shipyard_url)) return prefs.AuthenticationDialog(self.w, partial(self.verify, self.shipyard_url))
except companion.ServerError as e: except companion.ServerError as e:
self.status['text'] = str(e) self.status['text'] = str(e)
return return
@ -850,7 +848,7 @@ class AppWindow:
self.w.withdraw() # Following items can take a few seconds, so hide the main window while they happen self.w.withdraw() # Following items can take a few seconds, so hide the main window while they happen
hotkeymgr.unregister() hotkeymgr.unregister()
monitor.close() monitor.close()
eddn.close() self.eddn.close()
self.updater.close() self.updater.close()
self.session.close() self.session.close()
config.close() config.close()

88
eddn.py
View File

@ -25,48 +25,56 @@ from companion import category_map
timeout= 10 # requests timeout timeout= 10 # requests timeout
module_re = re.compile('^Hpt_|^Int_|_Armour_') module_re = re.compile('^Hpt_|^Int_|_Armour_')
replayfile = None # For delayed messages
class _EDDN: class EDDN:
### UPLOAD = 'http://localhost:8081/upload/' # testing ### UPLOAD = 'http://localhost:8081/upload/' # testing
UPLOAD = 'http://eddn-gateway.elite-markets.net:8080/upload/' UPLOAD = 'http://eddn-gateway.elite-markets.net:8080/upload/'
REPLAYPERIOD = 400 # Roughly two messages per second, accounting for send delays [ms]
REPLAYFLUSH = 20 # Update log on disk roughly every 10 seconds
def __init__(self): def __init__(self, parent):
self.parent = parent
self.session = requests.Session() self.session = requests.Session()
self.replayfile = None # For delayed messages self.replaylog = []
def load(self): def load(self):
# Try to obtain exclusive access to the journal cache # Try to obtain exclusive access to the journal cache
global replayfile
filename = join(config.app_dir, 'replay.jsonl') filename = join(config.app_dir, 'replay.jsonl')
try: try:
try: try:
# Try to open existing file # Try to open existing file
self.replayfile = open(filename, 'r+') replayfile = open(filename, 'r+')
except: except:
if exists(filename): if exists(filename):
raise # Couldn't open existing file raise # Couldn't open existing file
else: else:
self.replayfile = open(filename, 'w+') # Create file replayfile = open(filename, 'w+') # Create file
if platform != 'win32': # open for writing is automatically exclusive on Windows if platform != 'win32': # open for writing is automatically exclusive on Windows
lockf(self.replayfile, LOCK_EX|LOCK_NB) lockf(replayfile, LOCK_EX|LOCK_NB)
except: except:
if __debug__: print_exc() if __debug__: print_exc()
if self.replayfile: if replayfile:
self.replayfile.close() replayfile.close()
self.replayfile = None replayfile = None
return False return False
self.replaylog = [line.strip() for line in replayfile]
return True return True
def flush(self): def flush(self):
if self.replayfile or self.load(): replayfile.seek(0, SEEK_SET)
self.replayfile.seek(0, SEEK_SET) replayfile.truncate()
for line in self.replayfile: for line in self.replaylog:
self.send(*json.loads(line, object_pairs_hook=OrderedDict)) replayfile.write('%s\n' % line)
self.replayfile.truncate(0) replayfile.flush()
def close(self): def close(self):
if self.replayfile: global replayfile
self.replayfile.close() if replayfile:
replayfile.close()
replayfile = None
def send(self, cmdr, msg): def send(self, cmdr, msg):
msg['header'] = { msg['header'] = {
@ -85,6 +93,38 @@ class _EDDN:
print ('Content:\n%s' % r.text).encode('utf-8') print ('Content:\n%s' % r.text).encode('utf-8')
r.raise_for_status() r.raise_for_status()
def sendreplay(self):
if not replayfile:
return # Probably closing app
if not self.replaylog:
self.parent.status['text'] = ''
return
if len(self.replaylog) == 1:
self.parent.status['text'] = _('Sending data to EDDN...')
else:
self.parent.status['text'] = '%s [%d]' % (_('Sending data to EDDN...').replace('...',''), len(self.replaylog))
self.parent.w.update_idletasks()
try:
self.send(*json.loads(self.replaylog[0], object_pairs_hook=OrderedDict))
self.replaylog.pop(0)
if not len(self.replaylog) % self.REPLAYFLUSH:
self.flush()
except EnvironmentError as e:
if __debug__: print_exc()
self.parent.status['text'] = unicode(e)
except requests.exceptions.RequestException as e:
if __debug__: print_exc()
self.parent.status['text'] = _("Error: Can't connect to EDDN")
return # stop sending
except Exception as e:
if __debug__: print_exc()
self.parent.status['text'] = unicode(e)
return # stop sending
self.parent.w.after(self.REPLAYPERIOD, self.sendreplay)
def export_commodities(self, data): def export_commodities(self, data):
commodities = [] commodities = []
for commodity in data['lastStarport'].get('commodities') or []: for commodity in data['lastStarport'].get('commodities') or []:
@ -142,25 +182,23 @@ class _EDDN:
'$schemaRef' : 'http://schemas.elite-markets.net/eddn/journal/1' + (is_beta and '/test' or ''), '$schemaRef' : 'http://schemas.elite-markets.net/eddn/journal/1' + (is_beta and '/test' or ''),
'message' : entry 'message' : entry
} }
if self.replayfile or self.load(): if replayfile or self.load():
# Store the entry # Store the entry
self.replayfile.seek(0, SEEK_END) self.replaylog.append(json.dumps([cmdr.encode('utf-8'), msg]))
self.replayfile.write('%s\n' % json.dumps([cmdr.encode('utf-8'), msg])) replayfile.write('%s\n' % self.replaylog[-1])
self.replayfile.flush()
if entry['event'] == 'Docked' or not (config.getint('output') & config.OUT_SYS_DELAY): if entry['event'] == 'Docked' or not (config.getint('output') & config.OUT_SYS_DELAY):
# Try to send this and previous entries # Try to send this and previous entries
self.flush() self.sendreplay()
else: else:
# Can't access replay file! Send immediately. # Can't access replay file! Send immediately.
self.parent.status['text'] = _('Sending data to EDDN...')
self.parent.w.update_idletasks()
self.send(cmdr, msg) self.send(cmdr, msg)
self.parent.status['text'] = ''
def export_blackmarket(self, cmdr, is_beta, msg): def export_blackmarket(self, cmdr, is_beta, msg):
self.send(cmdr, { self.send(cmdr, {
'$schemaRef' : 'http://schemas.elite-markets.net/eddn/blackmarket/1' + (is_beta and '/test' or ''), '$schemaRef' : 'http://schemas.elite-markets.net/eddn/blackmarket/1' + (is_beta and '/test' or ''),
'message' : msg 'message' : msg
}) })
# singleton
eddn = _EDDN()

View File

@ -11,7 +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 eddn import eddn import eddn
from hotkey import hotkeymgr from hotkey import hotkeymgr
from l10n import Translations from l10n import Translations
from monitor import monitor from monitor import monitor