From 07c0f6bb313c740d96bf162e2e3a883c96a06df9 Mon Sep 17 00:00:00 2001 From: Jonathan Harris <jonathan@marginal.org.uk> Date: Tue, 2 Jan 2018 16:12:48 +0000 Subject: [PATCH] Batch startup events and retry on failure --- plugins/edsm.py | 134 +++++++++++++++++++++--------------------------- 1 file changed, 58 insertions(+), 76 deletions(-) diff --git a/plugins/edsm.py b/plugins/edsm.py index ca179f95..b583c94a 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -67,9 +67,6 @@ def plugin_start(): this.thread.daemon = True this.thread.start() - # Get the last Discarded events from EDSM - this.queue.put(('https://www.edsm.net/api-journal-v1/discard', {}, discarded_callback)) - return 'EDSM' def plugin_app(parent): @@ -194,22 +191,17 @@ def journal_entry(cmdr, is_beta, system, station, entry, state): this.lastlookup = False this.system.update_idletasks() - this.multicrew = bool(state['Role']) - - # If Discarded events still emtpy, retry - if not this.discardedEvents: - this.queue.put(('https://www.edsm.net/api-journal-v1/discard', {}, discarded_callback)) - # Send interesting events to EDSM - if config.getint('edsm_out') and not is_beta and not this.multicrew and credentials(cmdr): - try: - # Send the journal entry if not in the discarded events - if entry['event'] not in this.discardedEvents: - sendEntry(cmdr, system, station, entry, state) - - except Exception as e: - if __debug__: print_exc() - return unicode(e) + if config.getint('edsm_out') and not is_beta and not state['Role'] and credentials(cmdr) and entry['event'] not in this.discardedEvents: + # Introduce transient states into the event + transient = { + '_systemName': system, + #'_systemCoordinates': state['coordinates'], #TODO: Track system coordinates + '_stationName': station, + '_shipId': state['ShipID'], + } + entry.update(transient) + this.queue.put((cmdr, entry)) # Update system data @@ -231,27 +223,57 @@ def cmdr_data(data, is_beta): # Worker thread def worker(): + + pending = [] # Unsent events + while True: item = this.queue.get() if not item: return # Closing else: - (url, data, callback) = item + (cmdr, entry) = item retrying = 0 while retrying < 3: try: - r = this.session.post(url, data=data, timeout=_TIMEOUT) - r.raise_for_status() - reply = r.json() + if entry['event'] not in this.discardedEvents: + pending.append(entry) - if callback: - callback(reply) - else: + # Get list of events to discard + if not this.discardedEvents: + r = this.session.get('https://www.edsm.net/api-journal-v1/discard', timeout=_TIMEOUT) + r.raise_for_status() + this.discardedEvents = set(r.json()) + this.discardedEvents.discard('Docked') # should_send() assumes that we send 'Docked' events + assert this.discardedEvents # wouldn't expect this to be empty + pending = [x for x in pending if x['event'] not in this.discardedEvents] # Filter out unwanted events + + if should_send(pending): + (username, apikey) = credentials(cmdr) + data = { + 'commanderName': username.encode('utf-8'), + 'apiKey': apikey, + 'fromSoftware': applongname, + 'fromSoftwareVersion': appversion, + 'message': json.dumps(pending, ensure_ascii=False).encode('utf-8'), + } + r = this.session.post('https://www.edsm.net/api-journal-v1', data=data, timeout=_TIMEOUT) + r.raise_for_status() + reply = r.json() (msgnum, msg) = reply['msgnum'], reply['msg'] - if msgnum // 100 not in (1,4): - if __debug__: print(_('Error: EDSM {MSG}').format(MSG=msg)) + if msgnum // 100 != 1: # 1xx == OK + print('EDSM\t%s %s\t%s' % (msgnum, msg, json.dumps(pending, separators = (',', ': ')))) plug.show_error(_('Error: EDSM {MSG}').format(MSG=msg)) + else: + # Update main window's system status + for i in range(len(pending) - 1, -1, -1): + if pending[i]['event'] in ['Location', 'FSDJump']: + this.lastlookup = reply['events'][i] + this.system.event_generate('<<EDSMStatus>>', when="tail") # calls update_status in main thread + break + + pending = [] + break except: if __debug__: print_exc() @@ -263,45 +285,17 @@ def worker(): plug.show_error(_("Error: Can't connect to EDSM")) -# Queue a call to the EDSM journal API with args (which should be quoted) -def call(cmdr, args, callback=None): - (username, apikey) = credentials(cmdr) - args = dict(args) - args['commanderName'] = username.encode('utf-8') - args['apiKey'] = apikey - args['fromSoftware'] = applongname - args['fromSoftwareVersion'] = appversion - this.queue.put(('https://www.edsm.net/api-journal-v1', args, callback)) +# Whether any of the entries should be sent immediately +def should_send(entries): + for entry in entries: + # Skip entries that will be soon followed by a 'Docked' event + if (entry['event'] not in ['Cargo', 'Loadout', 'Materials', 'LoadGame', 'Rank', 'Progress', + 'ShipyardBuy', 'ShipyardNew', 'ShipyardSwap'] and + not (entry['event'] == 'Location' and entry.get('Docked'))): + return True + return False -# Send journal entry -def sendEntry(cmdr, system, station, entry, state): - if entry['event'] in this.discardedEvents: - return - - if entry['event'] in ['Location', 'FSDJump'] and entry['StarSystem'] in FAKE: - return - - # Update callback on needed events - if entry['event'] in ['Location', 'FSDJump']: - eventCallback = writelog_callback - else: - eventCallback = null_callback - - # Introduce transient states into the event - entry['_systemName'] = system - #entry['_systemCoordinates'] = gameStatus.coordinates #TODO: Track system coordinates - entry['_stationName'] = station - entry['_shipId'] = state['ShipID'] - - # Make the API call - call(cmdr, { 'message': json.dumps(entry, ensure_ascii=False).encode('utf-8') }, eventCallback) - - -def writelog_callback(reply): - this.lastlookup = reply['events'][0] # Get first response while we send events one by one - this.system.event_generate('<<EDSMStatus>>', when="tail") # calls update_status in main thread - def update_status(event=None): reply = this.lastlookup # Message numbers: 1xx = OK, 2xx = fatal error, 3xx = error (but not generated in practice), 4xx = ignorable errors @@ -316,15 +310,3 @@ def update_status(event=None): else: this.system['image'] = this._IMG_KNOWN - -# When we don't care about return msgnum from EDSM -def null_callback(reply): - if not reply: - plug.show_error(_("Error: Can't connect to EDSM")) - -# Grab the discarded list -def discarded_callback(reply): - if not reply: - plug.show_error(_("Error: Can't connect to EDSM")) - else: - this.discardedEvents = reply