1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-04-13 07:47:14 +03:00

Merge branch 'EDSM+EDDB'

This commit is contained in:
Jonathan Harris 2015-09-12 04:16:56 +01:00
commit c3f8aaa8f4
11 changed files with 210 additions and 8 deletions

View File

@ -8,6 +8,8 @@ from os.path import expanduser, isdir, join
import re
import requests
from time import time, localtime, strftime
import urllib
import webbrowser
import Tkinter as tk
import ttk
@ -21,16 +23,53 @@ import companion
import bpc
import td
import eddn
import edsm
import loadout
import coriolis
import flightlog
import eddb
import prefs
from config import appname, applongname, config
from hotkey import hotkeymgr
l10n.Translations().install()
EDDB = eddb.EDDB()
SHIPYARD_RETRY = 5 # retry pause for shipyard data [s]
EDSM_POLL = 0.1
class HyperlinkLabel(ttk.Label):
def __init__(self, master=None, **kw):
self.urlfn = kw.pop('urlfn', None)
ttk.Label.__init__(self, master, **kw)
self.font_n = kw.get('font', ttk.Style().lookup('TLabel', 'font'))
self.font_u = tkFont.Font(self, self.font_n)
self.font_u.configure(underline = True)
self.bind('<Enter>', self._enter)
self.bind('<Leave>', self._leave)
self.bind('<Button-1>', self._click)
# Make blue and clickable if setting non-empty text
def __setitem__(self, key, value):
if key=='text':
if self.urlfn and value:
self.configure({key: value}, foreground = 'blue', cursor = platform=='darwin' and 'pointinghand' or 'hand2')
else:
self.configure({key: value}, foreground = '', cursor = 'arrow')
else:
self.configure({key: value})
def _enter(self, event):
self.configure(font = self.font_u)
def _leave(self, event):
self.configure(font = self.font_n)
def _click(self, event):
if self.urlfn and self['text']:
webbrowser.open(self.urlfn(self['text']))
class AppWindow:
@ -39,6 +78,7 @@ class AppWindow:
self.holdofftime = config.getint('querytime') + companion.holdoff
self.session = companion.Session()
self.edsm = edsm.EDSM()
self.w = master
self.w.title(applongname)
@ -73,9 +113,9 @@ class AppWindow:
ttk.Label(frame, text=_('System:')).grid(row=1, column=0, sticky=tk.W) # Main window
ttk.Label(frame, text=_('Station:')).grid(row=2, column=0, sticky=tk.W) # Main window
self.cmdr = ttk.Label(frame, width=-20)
self.system = ttk.Label(frame, width=-20)
self.station = ttk.Label(frame, width=-20)
self.cmdr = ttk.Label(frame, width=-21)
self.system = HyperlinkLabel(frame, compound=tk.RIGHT, urlfn = self.system_url)
self.station = HyperlinkLabel(frame, urlfn = self.station_url)
self.button = ttk.Button(frame, text=_('Update'), command=self.getandsend, default=tk.ACTIVE, state=tk.DISABLED) # Update button in main window
self.status = ttk.Label(frame, width=-25)
self.w.bind('<Return>', self.getandsend)
@ -211,7 +251,8 @@ class AppWindow:
self.cmdr['text'] = data.get('commander') and data.get('commander').get('name') or ''
self.system['text'] = data.get('lastSystem') and data.get('lastSystem').get('name') or ''
self.station['text'] = data.get('commander') and data.get('commander').get('docked') and data.get('lastStarport') and data.get('lastStarport').get('name') or '-'
self.system['image'] = None
self.station['text'] = data.get('commander') and data.get('commander').get('docked') and data.get('lastStarport') and data.get('lastStarport').get('name') or (EDDB.system(self.system['text'] and '-' or ''))
config.set('querytime', querytime)
self.holdofftime = querytime + companion.holdoff
@ -229,9 +270,14 @@ class AppWindow:
elif (config.getint('output') & config.OUT_EDDN) and data['commander'].get('docked') and not data['lastStarport'].get('ships') and not retrying:
# API is flakey about shipyard info - retry if missing (<1s is usually sufficient - 5s for margin).
self.w.after(SHIPYARD_RETRY * 1000, lambda:self.getandsend(event, retrying=True))
self.w.after(int(SHIPYARD_RETRY * 1000), lambda:self.getandsend(event, retrying=True))
# Stuff we can do while waiting for retry
self.edsm.start_lookup(self.system['text'])
self.system['image'] = self.edsm.result['img']
self.w.after(int(EDSM_POLL * 1000), self.edsmpoll)
if config.getint('output') & config.OUT_LOG:
flightlog.export(data)
if config.getint('output') & config.OUT_SHIP_EDS:
@ -249,6 +295,10 @@ class AppWindow:
h.write(json.dumps(data, indent=2, sort_keys=True))
if not retrying:
self.edsm.start_lookup(self.system['text'])
self.system['image'] = self.edsm.result['img']
self.w.after(int(EDSM_POLL * 1000), self.edsmpoll)
if config.getint('output') & config.OUT_LOG:
flightlog.export(data)
if config.getint('output') & config.OUT_SHIP_EDS:
@ -318,6 +368,28 @@ class AppWindow:
self.cooldown()
def edsmpoll(self):
result = self.edsm.result
if result['done']:
self.system['image'] = result['img']
else:
self.w.after(int(EDSM_POLL * 1000), self.edsmpoll)
def system_url(self, text):
return text and self.edsm.result['url']
def station_url(self, text):
if text:
station_id = EDDB.station(self.system['text'], self.station['text'])
if station_id:
return 'http://eddb.io/station/%d' % station_id
system_id = EDDB.system(self.system['text'])
if system_id:
return 'http://eddb.io/system/%d' % system_id
return None
def cooldown(self):
if time() < self.holdofftime:
self.button['text'] = _('cooldown {SS}s').format(SS = int(self.holdofftime - time())) # Update button in main window

View File

@ -103,6 +103,12 @@
</Component>
<Component Guid="*">
<File KeyPath="yes" Source="SourceDir\snd_bad.wav" />
</Component>
<Component Guid="*">
<File KeyPath="yes" Source="SourceDir\stations.p" />
</Component>
<Component Guid="*">
<File KeyPath="yes" Source="SourceDir\systems.p" />
</Component>
<Component Guid="{30EEAD30-A43B-4A31-A209-450A8AD17AC2}">
<File KeyPath="yes" Source="SourceDir\tcl85.dll" />
@ -355,6 +361,8 @@
<ComponentRef Id="select.pyd" />
<ComponentRef Id="snd_good.wav" />
<ComponentRef Id="snd_bad.wav" />
<ComponentRef Id="stations.p" />
<ComponentRef Id="systems.p" />
<ComponentRef Id="tcl85.dll" />
<ComponentRef Id="tk85.dll" />
<ComponentRef Id="unicodedata.pyd" />

View File

@ -9,7 +9,11 @@ This app downloads commodity market and other data from the game [Elite: Dangero
The user-interface is deliberately minimal - when you land at a station just switch to the app and press the “Update” button or press Enter to automatically download and transmit and/or save your choice of data.
![Windows screenshot](img/win.png) ![Mac screenshot](img/mac.png)
Click on the system name to go to its [Elite: Dangerous Star Map](http://www.edsm.net/) (“EDSM”) entry in your web broswer.
Click on the station name to go to its [Elite: Dangerous Database](http://eddb.io/) (“eddb”) entry in your web broswer.
![Windows screenshot](img/win.png) &nbsp; ![Mac screenshot](img/mac.png)
Installation

View File

@ -1,7 +1,6 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
import json
import requests
from collections import defaultdict
from cookielib import LWPCookieJar
@ -197,7 +196,7 @@ class Session:
r.raise_for_status()
try:
data = json.loads(r.text)
data = r.json()
except:
self.dump(r)
raise ServerError()

53
eddb.py Executable file
View File

@ -0,0 +1,53 @@
#!/usr/bin/python
#
# eddb.io station database
#
import cPickle
from os.path import dirname, join, normpath
import sys
from sys import platform
class EDDB:
def __init__(self):
self.system_ids = cPickle.load(open(join(self.respath(), 'systems.p'), 'rb'))
self.station_ids = cPickle.load(open(join(self.respath(), 'stations.p'), 'rb'))
# system_name -> system_id
def system(self, system_name):
return self.system_ids.get(system_name, 0) # return 0 on failure (0 is not a valid id)
# (system_name, station_name) -> station_id
def station(self, system_name, station_name):
return self.station_ids.get((self.system_ids.get(system_name), station_name), 0) # return 0 on failure (0 is not a valid id)
def respath(self):
if getattr(sys, 'frozen', False):
if platform=='darwin':
return normpath(join(dirname(sys.executable), os.pardir, 'Resources'))
else:
return dirname(sys.executable)
elif __file__:
return dirname(__file__)
else:
return '.'
# build system & station database from files systems.json and stations_lite.json from http://eddb.io/api
if __name__ == "__main__":
import json
# system_name by system_id
systems = dict([(x['id'], str(x['name'])) for x in json.loads(open('systems.json').read())])
stations = json.loads(open('stations_lite.json').read())
# system_id by system_name - populated systems only
system_ids = dict([(systems[x['system_id']], x['system_id']) for x in stations])
cPickle.dump(system_ids, open('systems.p', 'wb'), protocol = cPickle.HIGHEST_PROTOCOL)
# station_id by (system_id, station_name)
station_ids = dict([((x['system_id'], str(x['name'])), x['id']) for x in stations])
cPickle.dump(station_ids, open('stations.p', 'wb'), protocol = cPickle.HIGHEST_PROTOCOL)

63
edsm.py Normal file
View File

@ -0,0 +1,63 @@
import requests
import threading
from sys import platform
import urllib
import Tkinter as tk
if __debug__:
from traceback import print_exc
class EDSM:
_TIMEOUT = 10
def __init__(self):
self.result = { 'img': None, 'url': None, 'done': True }
EDSM._IMG_WAIT_MAC = tk.PhotoImage(data = 'R0lGODlhDgAQAKEBAAAAAP///////////yH5BAEKAAIALAAAAAAOABAAAAIrlAWpx6jZzoPRvQqC3qBlzjGfNnbSFpQmQibcOqKpKIe0vIpTZS3Y/rscCgA7') # wristwatch
EDSM._IMG_WAIT_WIN = tk.PhotoImage(data = 'R0lGODlhDgAQAKEBAAAAAP///////////yH5BAEKAAIALAAAAAAOABAAAAIuFI4JwurcgpxhQUOnhUD2Xl1R5YmcZl5fqoYsVqYgKs527ZHu+ZGb4UhwgghGAQA7') # hourglass
EDSM._IMG_KNOWN = tk.PhotoImage(data = 'R0lGODlhDgAOAMIEAFWjVVWkVWS/ZGfFZwAAAAAAAAAAAAAAACH5BAEKAAQALAAAAAAOAA4AAAMsSLrcHEIEp8C4GDSLu15dOCyB2E2EYGKCoq5DS5QwSsDjwomfzlOziA0ITAAAOw==') # green circle
EDSM._IMG_UNKNOWN = tk.PhotoImage(data = 'R0lGODlhDgAOAMIEAM16BM57BfCPBfiUBgAAAAAAAAAAAAAAACH5BAEKAAQALAAAAAAOAA4AAAMsSLrcHEIEp8C4GDSLu15dOCyB2E2EYGKCoq5DS5QwSsDjwomfzlOziA0ITAAAOw==') # orange circle
EDSM._IMG_NOTFOUND = tk.PhotoImage(data = 'R0lGODlhDgAOAKECAGVLJ+ddWO5fW+5fWyH5BAEKAAMALAAAAAAOAA4AAAImnI+JEAFqgJj0LYqFNTkf2VVGEFLBWE7nAJZbKlzhFnX00twQVAAAOw==') # red circle
EDSM._IMG_NEW = tk.PhotoImage(data = 'R0lGODlhEAAQAMZwANKVHtWcIteiHuiqLPCuHOS1MN22ZeW7ROG6Zuu9MOy+K/i8Kf/DAuvCVf/FAP3BNf/JCf/KAPHHSv7ESObHdv/MBv/GRv/LGP/QBPXOPvjPQfjQSvbRSP/UGPLSae7Sfv/YNvLXgPbZhP7dU//iI//mAP/jH//kFv7fU//fV//ebv/iTf/iUv/kTf/iZ/vgiP/hc/vgjv/jbfriiPriiv7ka//if//jd//sJP/oT//tHv/mZv/sLf/rRP/oYv/rUv/paP/mhv/sS//oc//lkf/mif/sUf/uPv/qcv/uTv/uUv/vUP/qhP/xP//pm//ua//sf//ubf/wXv/thv/tif/slv/tjf/smf/yYP/ulf/2R//2Sv/xkP/2av/0gP/ylf/2df/0i//0j//0lP/5cP/7a//1p//5gf/7ev/3o//2sf/5mP/6kv/2vP/3y//+jP///////////////////////////////////////////////////////////////yH5BAEKAH8ALAAAAAAQABAAAAePgH+Cg4SFhoJKPIeHYT+LhVppUTiPg2hrUkKPXWdlb2xHJk9jXoNJQDk9TVtkYCUkOy4wNjdGfy1UXGJYOksnPiwgFwwYg0NubWpmX1ArHREOFYUyWVNIVkxXQSoQhyMoNVUpRU5EixkcMzQaGy8xhwsKHiEfBQkSIg+GBAcUCIIBBDSYYGiAAUMALFR6FAgAOw==')
EDSM._IMG_ERROR = tk.PhotoImage(data = 'R0lGODlhDgAOAIABAAAAAP///yH5BAEKAAEALAAAAAAOAA4AAAIcjAOpx+rAUGrzVHujWRrDvmWdOH5geKZqSmpkAQA7') # BBC Mode 7 '?'
def start_lookup(self, system_name):
self.cancel_lookup()
self.result = { 'img': None, 'url': 'http://www.edsm.net/needed-distances?systemName=%s' % urllib.quote(system_name), 'done': False } # default URL
self.thread = threading.Thread(target = self.worker, name = 'EDSM worker', args = (system_name, self.result))
self.thread.daemon = True
self.thread.start()
def cancel_lookup(self):
self.thread = None # orphan any existing thread
self.result = { 'img': None, 'url': None, 'done': True } # orphan existing thread's results
def worker(self, system_name, result):
try:
r = requests.get('http://www.edsm.net/api-v1/system?sysname=%s&coords=1' % urllib.quote(system_name), timeout=EDSM._TIMEOUT)
r.raise_for_status()
data = r.json()
if data == -1:
# System not present - create it
result['img'] = EDSM._IMG_NEW
result['done'] = True # give feedback immediately
requests.get('http://www.edsm.net/api-v1/url?sysname=%s' % urllib.quote(system_name), timeout=EDSM._TIMEOUT) # creates system
elif data.get('coords'):
# Prefer to send user to "Show distances" page for systems with known coordinates
result['img'] = EDSM._IMG_KNOWN
result['done'] = True # give feedback immediately
try:
r = requests.get('http://www.edsm.net/api-v1/url?sysname=%s' % urllib.quote(system_name), timeout=EDSM._TIMEOUT)
r.raise_for_status()
data = r.json()
result['url'] = data['url']['show-system'].replace('\\','')
except:
if __debug__: print_exc()
else:
result['img'] = EDSM._IMG_UNKNOWN
except:
if __debug__: print_exc()
result['img'] = EDSM._IMG_ERROR
result['done'] = True

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -65,6 +65,7 @@ if sys.platform=='darwin':
'excludes': [ 'PIL', 'simplejson' ],
'iconfile': '%s.icns' % APPNAME,
'resources': ['snd_good.wav', 'snd_bad.wav'],
'resources': ['stations.p', 'systems.p'],
'semi_standalone': True,
'site_packages': False,
'plist': {
@ -101,6 +102,8 @@ elif sys.platform=='win32':
'WinSparkle.pdb', # For debugging - don't include in package
'snd_good.wav',
'snd_bad.wav',
'stations.p',
'systems.p',
'%s.VisualElementsManifest.xml' % APPNAME,
'%s.ico' % APPNAME ] +
[join('L10n',x) for x in os.listdir('L10n') if x.endswith('.strings')] ) ]

BIN
stations.p Normal file

Binary file not shown.

BIN
systems.p Normal file

Binary file not shown.