1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-04-16 09:10:35 +03:00

Merge branch 'release-4.0.4' into stable

This commit is contained in:
Athanasius 2020-08-07 20:06:13 +01:00
commit 02cd8fc447
27 changed files with 199 additions and 69 deletions

View File

@ -1,6 +1,26 @@
This is the master changelog for Elite Dangerous Market Connector. Entries are in reverse chronological order (latest first).
---
Release 4.0.4
===
* Built using Python 3.7.8. Prior 4.0.x releases used 3.7.7.
* Don't crash if no non-default Journal Directory has been set.
* Only send to Inara API at most once every 30 seconds. This should avoid
the "Inara 400 Too much requests, slow down, cowboy. ;) ..." message and
being locked out from the API for an hour as a result. Any events that
require data to be sent during the 30s cooldown will be queued and sent when
that timer expires.
This was caused by previous changes in an attempt to send cargo events
to Inara more often. This fix retains that enhancement.
Note that if you log out and stop EDMC within 30 seconds you might have
some events not sent. If we tried to force a send then it might hit the
limit when you want to log back in and continue playing. As it is you can
re-run EDMC and log back into the game to ensure Inara is synchronised
properly.
Release 4.0.3
===

View File

@ -235,7 +235,7 @@
/* Appearance setting prompt. [prefs.py] */
"Language" = "Jazyk";
/* [EDMarketConnector.py] */
/* [EDMarketConnector.py] - Leave '{HH}:{MM}:{SS}' as-is */
"Last updated at {HH}:{MM}:{SS}" = "Poslední aktualizace {HH}:{MM}:{SS}";
/* Federation rank. [stats.py] */

View File

@ -235,7 +235,7 @@
/* Appearance setting prompt. [prefs.py] */
"Language" = "Sprache";
/* [EDMarketConnector.py] */
/* [EDMarketConnector.py] - Leave '{HH}:{MM}:{SS}' as-is */
"Last updated at {HH}:{MM}:{SS}" = "Zuletzt aktualisiert um {HH}:{MM}:{SS}";
/* Federation rank. [stats.py] */

View File

@ -490,6 +490,9 @@
/* Update button in main window. [EDMarketConnector.py] */
"Update" = "Update";
/* Option to use alternate URL method on shipyard links [prefs.py] */
"Use alternate URL method" = "Use alternate URL method";
/* Status dialog subtitle - CR value of ship. [stats.py] */
"Value" = "Value";

View File

@ -235,7 +235,7 @@
/* Appearance setting prompt. [prefs.py] */
"Language" = "Idioma";
/* [EDMarketConnector.py] */
/* [EDMarketConnector.py] - Leave '{HH}:{MM}:{SS}' as-is */
"Last updated at {HH}:{MM}:{SS}" = "Última actualización: {HH}:{MM}:{SS}";
/* Federation rank. [stats.py] */

View File

@ -235,7 +235,7 @@
/* Appearance setting prompt. [prefs.py] */
"Language" = "Kieli";
/* [EDMarketConnector.py] */
/* [EDMarketConnector.py] - Leave '{HH}:{MM}:{SS}' as-is */
"Last updated at {HH}:{MM}:{SS}" = "Päivitetty viimeksi {HH}:{MM}:{SS}";
/* Federation rank. [stats.py] */

View File

@ -235,7 +235,7 @@
/* Appearance setting prompt. [prefs.py] */
"Language" = "Langue";
/* [EDMarketConnector.py] */
/* [EDMarketConnector.py] - Leave '{HH}:{MM}:{SS}' as-is */
"Last updated at {HH}:{MM}:{SS}" = "Dernière mise à jour à {HH}:{MM}:{SS}";
/* Federation rank. [stats.py] */

View File

@ -232,7 +232,7 @@
/* Appearance setting prompt. [prefs.py] */
"Language" = "Nyelv";
/* [EDMarketConnector.py] */
/* [EDMarketConnector.py] - Leave '{HH}:{MM}:{SS}' as-is */
"Last updated at {HH}:{MM}:{SS}" = "Utoljára frissítve {HH}:{MM}:{SS}";
/* Federation rank. [stats.py] */

View File

@ -235,7 +235,7 @@
/* Appearance setting prompt. [prefs.py] */
"Language" = "Lingua";
/* [EDMarketConnector.py] */
/* [EDMarketConnector.py] - Leave '{HH}:{MM}:{SS}' as-is */
"Last updated at {HH}:{MM}:{SS}" = "Ultimo aggiornamento alle {HH}:{MM}:{SS}";
/* Federation rank. [stats.py] */

View File

@ -235,7 +235,7 @@
/* Appearance setting prompt. [prefs.py] */
"Language" = "言語";
/* [EDMarketConnector.py] */
/* [EDMarketConnector.py] - Leave '{HH}:{MM}:{SS}' as-is */
"Last updated at {HH}:{MM}:{SS}" = "最終更新時間 {HH}:{MM}:{SS}";
/* Federation rank. [stats.py] */

View File

@ -217,7 +217,7 @@
/* Appearance setting prompt. [prefs.py] */
"Language" = "Valoda";
/* [EDMarketConnector.py] */
/* [EDMarketConnector.py] - Leave '{HH}:{MM}:{SS}' as-is */
"Last updated at {HH}:{MM}:{SS}" = "Pēdējo reizi atjaunināts {HH}:{MM}:{SS}";
/* Federation rank. [stats.py] */

View File

@ -235,7 +235,7 @@
/* Appearance setting prompt. [prefs.py] */
"Language" = "Taal";
/* [EDMarketConnector.py] */
/* [EDMarketConnector.py] - Leave '{HH}:{MM}:{SS}' as-is */
"Last updated at {HH}:{MM}:{SS}" = "Voor het laatst bijgewerkt om {HH}:{MM}:{SS}";
/* Federation rank. [stats.py] */
@ -490,6 +490,9 @@
/* Update button in main window. [EDMarketConnector.py] */
"Update" = "Bijwerken";
/* Option to use alternate URL method on shipyard links [prefs.py] */
"Use alternate URL method" = "Gebruik andere URL methode";
/* Status dialog subtitle - CR value of ship. [stats.py] */
"Value" = "Waarde";

View File

@ -235,7 +235,7 @@
/* Appearance setting prompt. [prefs.py] */
"Language" = "Język";
/* [EDMarketConnector.py] */
/* [EDMarketConnector.py] - Leave '{HH}:{MM}:{SS}' as-is */
"Last updated at {HH}:{MM}:{SS}" = "Ostatnia aktualizacja {HH}:{MM}:{SS}";
/* Federation rank. [stats.py] */

View File

@ -235,7 +235,7 @@
/* Appearance setting prompt. [prefs.py] */
"Language" = "Idioma";
/* [EDMarketConnector.py] */
/* [EDMarketConnector.py] - Leave '{HH}:{MM}:{SS}' as-is */
"Last updated at {HH}:{MM}:{SS}" = "Última atualização {HH}:{MM}:{SS}";
/* Federation rank. [stats.py] */

View File

@ -235,7 +235,7 @@
/* Appearance setting prompt. [prefs.py] */
"Language" = "Linguagem";
/* [EDMarketConnector.py] */
/* [EDMarketConnector.py] - Leave '{HH}:{MM}:{SS}' as-is */
"Last updated at {HH}:{MM}:{SS}" = "Última actualização: {HH}:{MM}:{SS}";
/* Federation rank. [stats.py] */

View File

@ -235,7 +235,7 @@
/* Appearance setting prompt. [prefs.py] */
"Language" = "Язык";
/* [EDMarketConnector.py] */
/* [EDMarketConnector.py] - Leave '{HH}:{MM}:{SS}' as-is */
"Last updated at {HH}:{MM}:{SS}" = "Последнее обновление в {HH}:{MM}:{SS}";
/* Federation rank. [stats.py] */

View File

@ -184,7 +184,7 @@
/* Appearance setting prompt. [prefs.py] */
"Language" = "Jezik";
/* [EDMarketConnector.py] */
/* [EDMarketConnector.py] - Leave '{HH}:{MM}:{SS}' as-is */
"Last updated at {HH}:{MM}:{SS}" = "Zadnja osvežitev podatkov {HH}:{MM}:{SS} ";
/* Federation rank. [stats.py] */

View File

@ -235,7 +235,7 @@
/* Appearance setting prompt. [prefs.py] */
"Language" = "Jezik";
/* [EDMarketConnector.py] */
/* [EDMarketConnector.py] - Leave '{HH}:{MM}:{SS}' as-is */
"Last updated at {HH}:{MM}:{SS}" = "Posljednji put osvježeno {HH}:{MM}:{SS}";
/* Federation rank. [stats.py] */

View File

@ -235,7 +235,7 @@
/* Appearance setting prompt. [prefs.py] */
"Language" = "Jezik";
/* [EDMarketConnector.py] */
/* [EDMarketConnector.py] - Leave '{HH}:{MM}:{SS}' as-is */
"Last updated at {HH}:{MM}:{SS}" = "Poslednji put osveženo {HH}:{MM}:{SS}";
/* Federation rank. [stats.py] */

View File

@ -235,7 +235,7 @@
/* Appearance setting prompt. [prefs.py] */
"Language" = "Språk";
/* [EDMarketConnector.py] */
/* [EDMarketConnector.py] - Leave '{HH}:{MM}:{SS}' as-is */
"Last updated at {HH}:{MM}:{SS}" = "Senaste uppdatering: {HH}:{MM}:{SS}";
/* Federation rank. [stats.py] */

View File

@ -235,7 +235,7 @@
/* Appearance setting prompt. [prefs.py] */
"Language" = "Мова";
/* [EDMarketConnector.py] */
/* [EDMarketConnector.py] - Leave '{HH}:{MM}:{SS}' as-is */
"Last updated at {HH}:{MM}:{SS}" = "Останнє оновлення було {HH}:{MM}:{SS}";
/* Federation rank. [stats.py] */
@ -490,6 +490,9 @@
/* Update button in main window. [EDMarketConnector.py] */
"Update" = "Оновлення";
/* Option to use alternate URL method on shipyard links [prefs.py] */
"Use alternate URL method" = "Використовувати альтернативний URL-метод";
/* Status dialog subtitle - CR value of ship. [stats.py] */
"Value" = "Вартість";

View File

@ -232,7 +232,7 @@
/* Appearance setting prompt. [prefs.py] */
"Language" = "语言";
/* [EDMarketConnector.py] */
/* [EDMarketConnector.py] - Leave '{HH}:{MM}:{SS}' as-is */
"Last updated at {HH}:{MM}:{SS}" = "最后更新于 {HH}:{MM}:{SS}";
/* Federation rank. [stats.py] */

View File

@ -55,6 +55,72 @@ import myNotebook as nb
```
For creating UI elements.
---
### Logging
Currently (still in 4.0.3) the only way to provide any logged output from a
plugin is to use `print(...)` statements. When running the application from
the packaged executeable all output is redirected to a log file. See
[Reporting a problem](https://github.com/EDCD/EDMarketConnector/wiki/Troubleshooting#reporting-a-problem)
for the location of this log file.
A future version of EDMC will implement proper logging using the Python
`logging` module. Plugin developers should get their code ready for this by
using the following code instead of simple `print(...)` statements
Insert this at the top-level of your load.py file (so not inside
`plugin_start3()`):
```python
import logging
from config import appname
# This could also be returned from plugin_start3()
plugin_name = os.path.basename(os.path.dirname(__file__))
# A Logger is used per 'found' plugin to make it easy to include the plugin's
# folder name in the logging output format.
logger = logging.getLogger(f'{appname}.{plugin_name}')
# If the Logger has handlers then it was already set up by the core code, else
# it needs setting up here.
if not logger.hasHandlers():
level = logging.INFO # So logger.info(...) is equivalent to print()
logger.setLevel(level)
logger_channel = logging.StreamHandler()
logger_channel.setLevel(level)
logger_formatter = logging.Formatter(f'%(asctime)s - %(name)s - %(levelname)s - %(module)s:%(lineno)d:%(funcName)s: %(message)s')
logger_formatter.default_time_format = '%Y-%m-%d %H:%M:%S'
logger_formatter.default_msec_format = '%s.%03d'
logger_channel.setFormatter(logger_formatter)
logger.addHandler(logger_channel)
```
Then replace `print(...)` statements with one of the following:
```python
logger.info('some info message') # instead of print(...)
logger.debug('something only for debug')
logger.warning('Something needs warning about')
logger.error('Some error happened')
logger.critical('Something went wrong in a critical manner')
try:
...
except Exception:
# This logs at 'ERROR' level.
# Also automatically includes exception information.
logger.exception('An exception occurred')
try:
...
except Exception as e:
logger.debug('Exception we only note in debug output', exc_info=e)
```
---
### Startup

View File

@ -13,7 +13,7 @@ appcmdname = 'EDMC'
# appversion **MUST** follow Semantic Versioning rules:
# <https://semver.org/#semantic-versioning-specification-semver>
# Major.Minor.Patch(-prerelease)(+buildmetadata)
appversion = '4.0.3' #-rc1+a872b5f'
appversion = '4.0.4' #-rc1+a872b5f'
# For some things we want appversion without (possible) +build metadata
appversion_nobuild = str(semantic_version.Version(appversion).truncate('prerelease'))
copyright = u'© 2015-2019 Jonathan Harris, 2020 EDCD'

View File

@ -168,11 +168,36 @@
<!-- Windows -->
<item>
<title>Release 4.0.3</title>
<title>Release 4.0.4</title>
<description>
<![CDATA[
<style>body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; }</style>
<h2>Release 4.0.4</h2>
<ul>
<li>
<p>Built using Python 3.7.8. Prior 4.0.x releases used 3.7.7.</p>
</li>
<li>
<p>Don't crash if no non-default Journal Directory has been set.</p>
</li>
<li>
<p>Only send to Inara API at most once every 30 seconds. This should avoid
the "Inara 400 Too much requests, slow down, cowboy. ;) ..." message and
being locked out from the API for an hour as a result. Any events that
require data to be sent during the 30s cooldown will be queued and sent when
that timer expires.</p>
<p>This was caused by previous changes in an attempt to send cargo events
to Inara more often. This fix retains that enhancement.</p>
<p>Note that if you log out and stop EDMC within 30 seconds you might have
some events not sent. If we tried to force a send then it might hit the
limit when you want to log back in and continue playing. As it is you can
re-run EDMC and log back into the game to ensure Inara is synchronised
properly.</p>
</li>
</ul>
<h2>Release 4.0.3</h2>
<p><strong>NB: Anyone who installed a 4.0.3-rcX release candidate version should first
uninstall it before installing this.</strong>
@ -578,11 +603,11 @@ If any of your plugins are listed in that section then they will need updating,
]]>
</description>
<enclosure
url="https://github.com/EDCD/EDMarketConnector/releases/download/Release/4.0.3/EDMarketConnector_win_4.0.3.msi"
url="https://github.com/EDCD/EDMarketConnector/releases/download/Release/4.0.4/EDMarketConnector_win_4.0.4.msi"
sparkle:os="windows"
sparkle:installerArguments="/passive LAUNCH=yes"
sparkle:version="4.0.3"
length="11325440"
sparkle:version="4.0.4"
length="11419648"
type="application/octet-stream"
/>
</item>

View File

@ -21,7 +21,6 @@ import plug
if __debug__:
from traceback import print_exc
_TIMEOUT = 20
FAKE = ['CQC', 'Training', 'Destination'] # Fake systems that shouldn't be sent to Inara
CREDIT_RATIO = 1.05 # Update credits if they change by 5% over the course of a session
@ -50,8 +49,11 @@ this.loadout = None
this.fleet = None
this.shipswap = False # just swapped ship
this.last_update_time = time.time() # last time we updated (set to now because we're going to update quickly)
FLOOD_LIMIT_SECONDS = 45 # minimum time between sending non-major cargo triggered messages
# last time we updated, if unset in config this is 0, which means an instant update
LAST_UPDATE_CONF_KEY = 'inara_last_update'
# this.last_update_time = config.getint(LAST_UPDATE_CONF_KEY)
FLOOD_LIMIT_SECONDS = 30 # minimum time between sending events
this.timer_run = True
# Main window clicks
@ -86,6 +88,10 @@ def plugin_start3(plugin_dir):
this.thread = Thread(target = worker, name = 'Inara worker')
this.thread.daemon = True
this.thread.start()
this.timer_thread = Thread(target=call_timer, name='Inara timer')
this.timer_thread.daemon = True
this.timer_thread.start()
return 'Inara'
def plugin_app(parent):
@ -97,11 +103,14 @@ def plugin_app(parent):
def plugin_stop():
# Send any unsent events
call()
time.sleep(0.1) # Sleep for 100ms to allow call to go out, and to force a context switch to our other threads
# Signal thread to close and wait for it
this.queue.put(None)
this.thread.join()
this.thread = None
this.timer_run = False
def plugin_prefs(parent, cmdr, is_beta):
PADX = 10
@ -189,7 +198,7 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
# Send any unsent events when switching accounts
if cmdr and cmdr != this.cmdr:
call()
call(force=True)
this.cmdr = cmdr
this.FID = state['FID']
@ -246,8 +255,6 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
if config.getint('inara_out') and not is_beta and not this.multicrew and credentials(cmdr):
try:
old_events = len(this.events) # Will only send existing events if we add a new event below
# Dump starting state to Inara
if (this.newuser or
@ -306,6 +313,8 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
this.loadout = make_loadout(state)
add_event('setCommanderShipLoadout', entry['timestamp'], this.loadout)
call() # Call here just to be sure that if we can send, we do, otherwise it'll get it in the next tick
# Promotions
@ -435,35 +444,21 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
cargo = [OrderedDict([('itemName', k), ('itemCount', state['Cargo'][k])]) for k in sorted(state['Cargo'])]
# if our cargo differers from last we checked, we're at a station,
# and our flood limit isnt covered, queue an update
should_poll = this.cargo != cargo and time.time() - this.last_update_time > FLOOD_LIMIT_SECONDS
# Send event(s) to Inara
if entry['event'] == 'ShutDown' or len(this.events) > old_events or should_poll:
# Send cargo and materials if changed
if this.cargo != cargo:
add_event('setCommanderInventoryCargo', entry['timestamp'], cargo)
this.cargo = cargo
materials = []
for category in ['Raw', 'Manufactured', 'Encoded']:
materials.extend([ OrderedDict([('itemName', k), ('itemCount', state[category][k])]) for k in sorted(state[category]) ])
if this.materials != materials:
add_event('setCommanderInventoryMaterials', entry['timestamp'], materials)
this.materials = materials
# Queue a call to Inara
call()
# Send cargo and materials if changed
if this.cargo != cargo:
add_event('setCommanderInventoryCargo', entry['timestamp'], cargo)
this.cargo = cargo
materials = []
for category in ['Raw', 'Manufactured', 'Encoded']:
materials.extend([ OrderedDict([('itemName', k), ('itemCount', state[category][k])]) for k in sorted(state[category]) ])
if this.materials != materials:
add_event('setCommanderInventoryMaterials', entry['timestamp'], materials)
this.materials = materials
except Exception as e:
if __debug__: print_exc()
return str(e)
#
# Events that don't need to be sent immediately but will be sent on the next mandatory event
#
# Send credits and stats to Inara on startup only - otherwise may be out of date
if entry['event'] == 'LoadGame':
add_event('setCommanderCredits', entry['timestamp'],
@ -857,14 +852,22 @@ def add_event(name, timestamp, data):
('eventData', data),
]))
def call_timer(wait=FLOOD_LIMIT_SECONDS):
while this.timer_run:
time.sleep(wait)
if this.timer_run: # check again in here just in case we're closing and the stars align
call()
# Queue a call to Inara, handled in Worker thread
def call(callback=None):
def call(callback=None, force=False):
if not this.events:
return
this.last_update_time = time.time()
if (time.time() - config.getint(LAST_UPDATE_CONF_KEY)) <= FLOOD_LIMIT_SECONDS and not force:
return
config.set(LAST_UPDATE_CONF_KEY, int(time.time()))
print(f"INARA: {time.asctime()} sending {len(this.events)} entries to inara (call)")
data = OrderedDict([
('header', OrderedDict([
('appName', applongname),
@ -887,6 +890,8 @@ def worker():
else:
(url, data, callback) = item
print(f"INARA: {time.asctime()} sending {len(data['events'])} entries to inara (worker)")
retrying = 0
while retrying < 3:
try:
@ -896,28 +901,35 @@ def worker():
status = reply['header']['eventStatus']
if callback:
callback(reply)
elif status // 100 != 2: # 2xx == OK (maybe with warnings)
elif status // 100 != 2: # 2xx == OK (maybe with warnings)
# Log fatal errors
print('Inara\t%s %s' % (reply['header']['eventStatus'], reply['header'].get('eventStatusText', '')))
print(json.dumps(data, indent=2, separators = (',', ': ')))
plug.show_error(_('Error: Inara {MSG}').format(MSG = reply['header'].get('eventStatusText', status)))
plug.show_error(_('Error: Inara {MSG}').format(MSG=reply['header'].get('eventStatusText', status)))
else:
# Log individual errors and warnings
for data_event, reply_event in zip(data['events'], reply['events']):
if reply_event['eventStatus'] != 200:
print('Inara\t%s %s\t%s' % (reply_event['eventStatus'], reply_event.get('eventStatusText', ''), json.dumps(data_event)))
if reply_event['eventStatus'] // 100 != 2:
plug.show_error(_('Error: Inara {MSG}').format(MSG = '%s, %s' % (data_event['eventName'], reply_event.get('eventStatusText', reply_event['eventStatus']))))
if data_event['eventName'] in ['addCommanderTravelCarrierJump', 'addCommanderTravelDock', 'addCommanderTravelFSDJump', 'setCommanderTravelLocation']:
plug.show_error(_('Error: Inara {MSG}').format(
MSG=f'{data_event["eventName"]},'
f'{reply_event.get("eventStatusText", reply_event["eventStatus"])}'))
if data_event['eventName'] in ('addCommanderTravelCarrierJump',
'addCommanderTravelDock',
'addCommanderTravelFSDJump',
'setCommanderTravelLocation'):
this.lastlocation = reply_event.get('eventData', {})
this.system_link.event_generate('<<InaraLocation>>', when="tail") # calls update_location in main thread
# calls update_location in main thread
this.system_link.event_generate('<<InaraLocation>>', when="tail")
elif data_event['eventName'] in ['addCommanderShip', 'setCommanderShip']:
this.lastship = reply_event.get('eventData', {})
this.system_link.event_generate('<<InaraShip>>', when="tail") # calls update_ship in main thread
# calls update_ship in main thread
this.system_link.event_generate('<<InaraShip>>', when="tail")
break
except:
if __debug__: print_exc()
except Exception:
logger.debug('Sending events', exc_info=e)
retrying += 1
else:
if callback:

View File

@ -1,6 +1,4 @@
certifi==2019.9.11
requests>=2.11.1
watchdog>=0.8.3
# argh==0.26.2 watchdog dep
# pyyaml==5.3.1 watchdog dep
semantic-version==2.8.5
semantic-version>=2.8.5