diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 98eb2ffa..ea1e2dde 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -43,7 +43,7 @@ import companion import commodity from commodity import COMMODITY_CSV import td -from eddn import eddn +import eddn import edsm import coriolis import eddb @@ -76,6 +76,7 @@ class AppWindow: self.holdofftime = config.getint('querytime') + companion.holdoff self.session = companion.Session() self.edsm = edsm.EDSM() + self.eddn = eddn.EDDN(self) self.w = master 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 # 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 # callback after the Preferences dialog is applied @@ -496,13 +497,13 @@ class AppWindow: if not old_status: self.status['text'] = _('Sending data to EDDN...') self.w.update_idletasks() - eddn.export_commodities(data) - eddn.export_outfitting(data) + self.eddn.export_commodities(data) + self.eddn.export_outfitting(data) 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). self.w.after(int(SERVER_RETRY * 1000), self.retry_for_shipyard) else: - eddn.export_shipyard(data) + self.eddn.export_shipyard(data) if not old_status: self.status['text'] = '' @@ -578,7 +579,7 @@ class AppWindow: if __debug__: 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 - eddn.export_shipyard(data) + self.eddn.export_shipyard(data) except: pass @@ -704,10 +705,7 @@ class AppWindow: if 'StarPos' not in entry: entry['StarPos'] = list(monitor.coordinates) - self.status['text'] = _('Sending data to EDDN...') - self.w.update_idletasks() - eddn.export_journal_entry(monitor.cmdr, monitor.is_beta, entry) - self.status['text'] = '' + self.eddn.export_journal_entry(monitor.cmdr, monitor.is_beta, entry) elif (config.getint('output') & config.OUT_MKT_EDDN and monitor.cmdr and entry['event'] == 'MarketSell' and entry.get('BlackMarket')): @@ -723,7 +721,7 @@ class AppWindow: self.status['text'] = _('Sending data to EDDN...') 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'] = '' except requests.exceptions.RequestException as e: @@ -756,7 +754,7 @@ class AppWindow: try: data = self.session.query() 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: self.status['text'] = str(e) 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 hotkeymgr.unregister() monitor.close() - eddn.close() + self.eddn.close() self.updater.close() self.session.close() config.close() diff --git a/eddn.py b/eddn.py index 6ab63c5c..dd945659 100644 --- a/eddn.py +++ b/eddn.py @@ -25,48 +25,56 @@ from companion import category_map timeout= 10 # requests timeout module_re = re.compile('^Hpt_|^Int_|_Armour_') +replayfile = None # For delayed messages -class _EDDN: +class EDDN: ### UPLOAD = 'http://localhost:8081/upload/' # testing 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.replayfile = None # For delayed messages + self.replaylog = [] def load(self): # Try to obtain exclusive access to the journal cache + global replayfile filename = join(config.app_dir, 'replay.jsonl') try: try: # Try to open existing file - self.replayfile = open(filename, 'r+') + replayfile = open(filename, 'r+') except: if exists(filename): raise # Couldn't open existing file 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 - lockf(self.replayfile, LOCK_EX|LOCK_NB) + lockf(replayfile, LOCK_EX|LOCK_NB) except: if __debug__: print_exc() - if self.replayfile: - self.replayfile.close() - self.replayfile = None + if replayfile: + replayfile.close() + replayfile = None return False + self.replaylog = [line.strip() for line in replayfile] return True def flush(self): - if self.replayfile or self.load(): - self.replayfile.seek(0, SEEK_SET) - for line in self.replayfile: - self.send(*json.loads(line, object_pairs_hook=OrderedDict)) - self.replayfile.truncate(0) + replayfile.seek(0, SEEK_SET) + replayfile.truncate() + for line in self.replaylog: + replayfile.write('%s\n' % line) + replayfile.flush() def close(self): - if self.replayfile: - self.replayfile.close() + global replayfile + if replayfile: + replayfile.close() + replayfile = None def send(self, cmdr, msg): msg['header'] = { @@ -85,6 +93,38 @@ class _EDDN: print ('Content:\n%s' % r.text).encode('utf-8') 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): commodities = [] 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 ''), 'message' : entry } - if self.replayfile or self.load(): + if replayfile or self.load(): # Store the entry - self.replayfile.seek(0, SEEK_END) - self.replayfile.write('%s\n' % json.dumps([cmdr.encode('utf-8'), msg])) - self.replayfile.flush() + self.replaylog.append(json.dumps([cmdr.encode('utf-8'), msg])) + replayfile.write('%s\n' % self.replaylog[-1]) if entry['event'] == 'Docked' or not (config.getint('output') & config.OUT_SYS_DELAY): # Try to send this and previous entries - self.flush() + self.sendreplay() else: # 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.parent.status['text'] = '' def export_blackmarket(self, cmdr, is_beta, msg): self.send(cmdr, { '$schemaRef' : 'http://schemas.elite-markets.net/eddn/blackmarket/1' + (is_beta and '/test' or ''), 'message' : msg }) - - -# singleton -eddn = _EDDN() diff --git a/prefs.py b/prefs.py index e35495ed..9101cbfa 100644 --- a/prefs.py +++ b/prefs.py @@ -11,7 +11,7 @@ from ttkHyperlinkLabel import HyperlinkLabel import myNotebook as nb from config import applongname, config -from eddn import eddn +import eddn from hotkey import hotkeymgr from l10n import Translations from monitor import monitor