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

Multi-account support

Fixes #121
This commit is contained in:
Jonathan Harris 2017-01-21 21:40:15 +00:00
parent d7aae5dee3
commit a67eb982d8
8 changed files with 201 additions and 92 deletions

View File

@ -82,6 +82,9 @@ try:
config.set('querytime', getmtime(args.j)) config.set('querytime', getmtime(args.j))
else: else:
session = companion.Session() session = companion.Session()
if config.get('cmdrs'):
session.login(config.get('fdev_usernames')[0], config.get('fdev_passwords')[0])
else: # <= 2.25 not yet migrated
session.login(config.get('username'), config.get('password')) session.login(config.get('username'), config.get('password'))
querytime = int(time()) querytime = int(time())
data = session.query() data = session.query()

View File

@ -236,8 +236,6 @@ class AppWindow:
theme.register(self.theme_close) theme.register(self.theme_close)
theme.register_alternate((self.menubar, self.theme_menubar), {'row':0, 'columnspan':2, 'sticky':tk.NSEW}) theme.register_alternate((self.menubar, self.theme_menubar), {'row':0, 'columnspan':2, 'sticky':tk.NSEW})
self.set_labels()
# update geometry # update geometry
if config.get('geometry'): if config.get('geometry'):
match = re.match('\+([\-\d]+)\+([\-\d]+)', config.get('geometry')) match = re.match('\+([\-\d]+)\+([\-\d]+)', config.get('geometry'))
@ -274,17 +272,25 @@ class AppWindow:
# Load updater after UI creation (for WinSparkle) # Load updater after UI creation (for WinSparkle)
import update import update
self.updater = update.Updater(self.w) self.updater = update.Updater(self.w)
if not getattr(sys, 'frozen', False):
self.updater.checkForUpdates() # Sparkle / WinSparkle does this automatically for packaged apps
# First run self.postprefs() # Companion login happens in callback from monitor
if not config.get('username') or not config.get('password'):
prefs.PreferencesDialog(self.w, self.postprefs) # Try to obtain exclusive lock on journal cache, even if we don't need it yet
else: if not eddn.load():
self.login() 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
def postprefs(self): def postprefs(self):
self.set_labels() # in case language has changed self.set_labels() # in case language has changed
self.login() # in case credentials gave changed
# (Re-)install hotkey monitoring
hotkeymgr.register(self.w, config.getint('hotkey_code'), config.getint('hotkey_mods'))
# (Re-)install log monitoring
if not monitor.start(self.w):
self.status['text'] = 'Error: Check %s' % _('E:D journal file location') # Location of the new Journal file in E:D 2.2
# set main window labels, e.g. after language change # set main window labels, e.g. after language change
def set_labels(self): def set_labels(self):
@ -321,11 +327,15 @@ class AppWindow:
self.edit_menu.entryconfigure(0, label=_('Copy')) # As in Copy and Paste self.edit_menu.entryconfigure(0, label=_('Copy')) # As in Copy and Paste
def login(self): def login(self):
if not self.status['text']:
self.status['text'] = _('Logging in...') self.status['text'] = _('Logging in...')
self.button['state'] = self.theme_button['state'] = tk.DISABLED self.button['state'] = self.theme_button['state'] = tk.DISABLED
self.w.update_idletasks() self.w.update_idletasks()
try: try:
self.session.login(config.get('username'), config.get('password')) if not monitor.cmdr or monitor.cmdr not in config.get('cmdrs'):
raise companion.CredentialsError()
idx = config.get('cmdrs').index(monitor.cmdr)
self.session.login(config.get('fdev_usernames')[idx], config.get('fdev_passwords')[idx])
self.status['text'] = '' self.status['text'] = ''
except companion.VerificationRequired: except companion.VerificationRequired:
return prefs.AuthenticationDialog(self.w, partial(self.verify, self.login)) return prefs.AuthenticationDialog(self.w, partial(self.verify, self.login))
@ -334,21 +344,6 @@ class AppWindow:
except Exception as e: except Exception as e:
if __debug__: print_exc() if __debug__: print_exc()
self.status['text'] = unicode(e) self.status['text'] = unicode(e)
if not getattr(sys, 'frozen', False):
self.updater.checkForUpdates() # Sparkle / WinSparkle does this automatically for packaged apps
# Try to obtain exclusive lock on journal cache, even if we don't need it yet
if not eddn.load():
self.status['text'] = 'Error: Is another copy of this app already running?' # Shouldn't happen - don't bother localizing
# (Re-)install hotkey monitoring
hotkeymgr.register(self.w, config.getint('hotkey_code'), config.getint('hotkey_mods'))
# (Re-)install log monitoring
if not monitor.start(self.w):
self.status['text'] = 'Error: Check %s' % _('E:D journal file location') # Location of the new Journal file in E:D 2.2
self.cooldown() self.cooldown()
# callback after verification code # callback after verification code
@ -368,7 +363,7 @@ class AppWindow:
auto_update = not event auto_update = not event
play_sound = (auto_update or int(event.type) == self.EVENT_VIRTUAL) and not config.getint('hotkey_mute') play_sound = (auto_update or int(event.type) == self.EVENT_VIRTUAL) and not config.getint('hotkey_mute')
if (monitor.cmdr and not monitor.mode) or monitor.is_beta: if not monitor.cmdr or not monitor.mode or monitor.is_beta:
return # In CQC or Beta - do nothing return # In CQC or Beta - do nothing
if not retrying: if not retrying:
@ -478,7 +473,7 @@ class AppWindow:
self.status['text'] = '' self.status['text'] = ''
# Update credits and ship info and send to EDSM # Update credits and ship info and send to EDSM
if config.getint('output') & config.OUT_SYS_EDSM and not monitor.is_beta: if config.getint('output') & config.OUT_SYS_EDSM:
try: try:
if data['commander'].get('credits') is not None: if data['commander'].get('credits') is not None:
monitor.credits = (data['commander']['credits'], data['commander'].get('debt', 0)) monitor.credits = (data['commander']['credits'], data['commander'].get('debt', 0))
@ -568,13 +563,25 @@ class AppWindow:
self.edsm.link(monitor.system) self.edsm.link(monitor.system)
self.w.update_idletasks() self.w.update_idletasks()
# New Cmdr?
if entry['event'] in [None, 'NewCommander', 'LoadGame'] and monitor.cmdr and not monitor.is_beta:
prefs.migrate(monitor.cmdr) # migration from <= 2.25
if config.get('cmdrs') and monitor.cmdr in config.get('cmdrs'):
prefs.make_current(monitor.cmdr)
elif config.get('cmdrs') and entry['event'] == 'NewCommander':
cmdrs = config.get('cmdrs')
cmdrs[0] = monitor.cmdr # New Cmdr uses same credentials as old
config.set('cmdrs', cmdrs)
else:
prefs.PreferencesDialog(self.w, self.postprefs) # First run or new Cmdr
# Send interesting events to EDSM # Send interesting events to EDSM
if config.getint('output') & config.OUT_SYS_EDSM and not monitor.is_beta: if config.getint('output') & config.OUT_SYS_EDSM and not monitor.is_beta:
self.status['text'] = _('Sending data to EDSM...') self.status['text'] = _('Sending data to EDSM...')
self.w.update_idletasks() self.w.update_idletasks()
try: try:
# Update system status on startup # Update system status on startup
if monitor.mode and not entry['event']: if monitor.mode and monitor.system and not entry['event']:
self.edsm.lookup(monitor.system) self.edsm.lookup(monitor.system)
# Send credits to EDSM on new game (but not on startup - data might be old) # Send credits to EDSM on new game (but not on startup - data might be old)
@ -609,6 +616,10 @@ class AppWindow:
self.status['text'] = '' self.status['text'] = ''
self.edsmpoll() self.edsmpoll()
# Companion login - do this after EDSM so any EDSM errors don't mask login errors
if entry['event'] in [None, 'LoadGame'] and monitor.cmdr and not monitor.is_beta:
self.login()
if not entry['event'] or not monitor.mode: if not entry['event'] or not monitor.mode:
return # Startup or in CQC return # Startup or in CQC
@ -688,7 +699,7 @@ class AppWindow:
def shipyard_url(self, shipname=None): def shipyard_url(self, shipname=None):
if (monitor.cmdr and not monitor.mode) or monitor.is_beta: if not monitor.cmdr or not monitor.mode or monitor.is_beta:
return False # In CQC or Beta - do nothing return False # In CQC or Beta - do nothing
self.status['text'] = _('Fetching data...') self.status['text'] = _('Fetching data...')

View File

@ -144,6 +144,9 @@ If 2 this problem may or may not resolve itself in time.
This problem is tracked as [Issue #165](https://github.com/Marginal/EDMarketConnector/issues/165). This problem is tracked as [Issue #165](https://github.com/Marginal/EDMarketConnector/issues/165).
### Credentials settings are greyed out
You can't edit your Username/Password or EDSM Commander Name/API Key while Elite: Dangerous is at the Main Menu. You will be able to edit these values once you've entered the game.
### Error: Can't connect to EDDN ### Error: Can't connect to EDDN
EDMC needs to talk to eddn-gateway.elite-markets.net on port 8080. If you consistently receive this error check that your router or VPN configuration allows port 8080 / tcp outbound. EDMC needs to talk to eddn-gateway.elite-markets.net on port 8080. If you consistently receive this error check that your router or VPN configuration allows port 8080 / tcp outbound.

View File

@ -1,9 +1,10 @@
import requests import requests
from collections import defaultdict from collections import defaultdict
from cookielib import LWPCookieJar from cookielib import LWPCookieJar
import hashlib
import numbers import numbers
import os import os
from os.path import dirname, join from os.path import dirname, isfile, join
import sys import sys
from sys import platform from sys import platform
import time import time
@ -178,6 +179,7 @@ class Session:
def __init__(self): def __init__(self):
self.state = Session.STATE_INIT self.state = Session.STATE_INIT
self.credentials = None self.credentials = None
self.session = None
# yuck suppress InsecurePlatformWarning under Python < 2.7.9 which lacks SNI support # yuck suppress InsecurePlatformWarning under Python < 2.7.9 which lacks SNI support
try: try:
@ -189,14 +191,6 @@ class Session:
if platform=='win32' and getattr(sys, 'frozen', False): if platform=='win32' and getattr(sys, 'frozen', False):
os.environ['REQUESTS_CA_BUNDLE'] = join(dirname(sys.executable), 'cacert.pem') os.environ['REQUESTS_CA_BUNDLE'] = join(dirname(sys.executable), 'cacert.pem')
self.session = requests.Session()
self.session.headers['User-Agent'] = 'Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Mobile/11D257'
self.session.cookies = LWPCookieJar(join(config.app_dir, 'cookies.txt'))
try:
self.session.cookies.load()
except IOError:
pass
def login(self, username=None, password=None): def login(self, username=None, password=None):
if (not username or not password): if (not username or not password):
if not self.credentials: if not self.credentials:
@ -208,8 +202,20 @@ class Session:
if self.credentials == credentials and self.state == Session.STATE_OK: if self.credentials == credentials and self.state == Session.STATE_OK:
return # already logged in return # already logged in
if self.credentials and self.credentials['email'] != credentials['email']: # changed account
self.session.cookies.clear() if not self.credentials or self.credentials['email'] != credentials['email']: # changed account
self.close()
self.session = requests.Session()
self.session.headers['User-Agent'] = 'Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Mobile/11D257'
cookiefile = join(config.app_dir, 'cookies-%s.txt' % hashlib.md5(credentials['email']).hexdigest())
if not isfile(cookiefile) and isfile(join(config.app_dir, 'cookies.txt')):
os.rename(join(config.app_dir, 'cookies.txt'), cookiefile) # migration from <= 2.25
self.session.cookies = LWPCookieJar(cookiefile)
try:
self.session.cookies.load()
except IOError:
pass
self.credentials = credentials self.credentials = credentials
self.state = Session.STATE_INIT self.state = Session.STATE_INIT
try: try:
@ -237,7 +243,7 @@ class Session:
r = self.session.post(URL_CONFIRM, data = {'code' : code}, timeout=timeout) r = self.session.post(URL_CONFIRM, data = {'code' : code}, timeout=timeout)
if r.status_code != requests.codes.ok or r.url == URL_CONFIRM: # would have redirected away if success if r.status_code != requests.codes.ok or r.url == URL_CONFIRM: # would have redirected away if success
raise VerificationRequired() raise VerificationRequired()
self.save() # Save cookies now for use by command-line app self.session.cookies.save() # Save cookies now for use by command-line app
self.login() self.login()
def query(self): def query(self):
@ -270,16 +276,14 @@ class Session:
return data return data
def save(self):
self.session.cookies.save()
def close(self): def close(self):
self.state = Session.STATE_NONE self.state = Session.STATE_NONE
if self.session:
try: try:
self.session.cookies.save() self.session.cookies.save()
self.session.close() self.session.close()
except: except:
pass if __debug__: print_exc()
self.session = None self.session = None
def dump(self, r): def dump(self, r):

View File

@ -7,6 +7,7 @@ import urllib2
import Tkinter as tk import Tkinter as tk
from config import appname, applongname, appversion, config from config import appname, applongname, appversion, config
from monitor import monitor
if __debug__: if __debug__:
from traceback import print_exc from traceback import print_exc
@ -79,10 +80,11 @@ class EDSM:
# Call an EDSM endpoint with args (which should be quoted) # Call an EDSM endpoint with args (which should be quoted)
def call(self, endpoint, args, check_msgnum=True): def call(self, endpoint, args, check_msgnum=True):
try: try:
idx = config.get('cmdrs').index(monitor.cmdr)
url = 'https://www.edsm.net/%s?commanderName=%s&apiKey=%s&fromSoftware=%s&fromSoftwareVersion=%s' % ( url = 'https://www.edsm.net/%s?commanderName=%s&apiKey=%s&fromSoftware=%s&fromSoftwareVersion=%s' % (
endpoint, endpoint,
urllib2.quote(config.get('edsm_cmdrname').encode('utf-8')), urllib2.quote(config.get('edsm_usernames')[idx].encode('utf-8')),
urllib2.quote(config.get('edsm_apikey')), urllib2.quote(config.get('edsm_apikeys')[idx]),
urllib2.quote(applongname), urllib2.quote(applongname),
urllib2.quote(appversion), urllib2.quote(appversion),
) + args ) + args

View File

@ -106,7 +106,7 @@ class EDLogs(FileSystemEventHandler):
# Latest pre-existing logfile - e.g. if E:D is already running. Assumes logs sort alphabetically. # Latest pre-existing logfile - e.g. if E:D is already running. Assumes logs sort alphabetically.
# Do this before setting up the observer in case the journal directory has gone away # Do this before setting up the observer in case the journal directory has gone away
try: try:
logfiles = sorted([x for x in listdir(self.currentdir) if x.startswith('Journal.')]) logfiles = sorted([x for x in listdir(self.currentdir) if x.startswith('Journal.') and x.endswith('.log')])
self.logfile = logfiles and join(self.currentdir, logfiles[-1]) or None self.logfile = logfiles and join(self.currentdir, logfiles[-1]) or None
except: except:
self.logfile = None self.logfile = None
@ -129,6 +129,8 @@ class EDLogs(FileSystemEventHandler):
print '%s "%s"' % (polling and 'Polling' or 'Monitoring', self.currentdir) print '%s "%s"' % (polling and 'Polling' or 'Monitoring', self.currentdir)
print 'Start logfile "%s"' % self.logfile print 'Start logfile "%s"' % self.logfile
self.event_queue.append(None) # Generate null event to signal (re)start
if not self.running(): if not self.running():
self.thread = threading.Thread(target = self.worker, name = 'Journal worker') self.thread = threading.Thread(target = self.worker, name = 'Journal worker')
self.thread.daemon = True self.thread.daemon = True
@ -181,8 +183,6 @@ class EDLogs(FileSystemEventHandler):
except: except:
if __debug__: if __debug__:
print 'Invalid journal entry "%s"' % repr(line) print 'Invalid journal entry "%s"' % repr(line)
self.event_queue.append(None) # Generate null event to signal start
self.root.event_generate('<<JournalEvent>>', when="tail")
else: else:
loghandle = None loghandle = None

154
prefs.py
View File

@ -80,12 +80,13 @@ class PreferencesDialog(tk.Toplevel):
parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility') parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility')
self.resizable(tk.FALSE, tk.FALSE) self.resizable(tk.FALSE, tk.FALSE)
style = ttk.Style() self.cmdr = None # Note if Cmdr changes in the Journal
frame = ttk.Frame(self) frame = ttk.Frame(self)
frame.grid(sticky=tk.NSEW) frame.grid(sticky=tk.NSEW)
notebook = nb.Notebook(frame) notebook = nb.Notebook(frame)
notebook.bind('<<NotebookTabChanged>>', self.outvarchanged) # Recompute on tab change
PADX = 10 PADX = 10
BUTTONX = 12 # indent Checkbuttons and Radiobuttons BUTTONX = 12 # indent Checkbuttons and Radiobuttons
@ -96,17 +97,23 @@ class PreferencesDialog(tk.Toplevel):
nb.Label(credframe, text=_('Credentials')).grid(padx=PADX, sticky=tk.W) # Section heading in settings nb.Label(credframe, text=_('Credentials')).grid(padx=PADX, sticky=tk.W) # Section heading in settings
ttk.Separator(credframe, orient=tk.HORIZONTAL).grid(columnspan=2, padx=PADX, pady=PADY, sticky=tk.EW) ttk.Separator(credframe, orient=tk.HORIZONTAL).grid(columnspan=2, padx=PADX, pady=PADY, sticky=tk.EW)
nb.Label(credframe, text=_('Please log in with your Elite: Dangerous account details')).grid(padx=PADX, columnspan=2, sticky=tk.W) # Use same text as E:D Launcher's login dialog self.cred_label = nb.Label(credframe, text=_('Please log in with your Elite: Dangerous account details')) # Use same text as E:D Launcher's login dialog
nb.Label(credframe, text=_('Username (Email)')).grid(row=10, padx=PADX, sticky=tk.W) # Use same text as E:D Launcher's login dialog self.cred_label.grid(padx=PADX, columnspan=2, sticky=tk.W)
nb.Label(credframe, text=_('Password')).grid(row=11, padx=PADX, sticky=tk.W) # Use same text as E:D Launcher's login dialog self.cmdr_label = nb.Label(credframe, text=_('Cmdr')) # Main window
self.cmdr_label.grid(row=10, padx=PADX, sticky=tk.W)
self.username_label = nb.Label(credframe, text=_('Username (Email)')) # Use same text as E:D Launcher's login dialog
self.username_label.grid(row=11, padx=PADX, sticky=tk.W)
self.password_label = nb.Label(credframe, text=_('Password')) # Use same text as E:D Launcher's login dialog
self.password_label.grid(row=12, padx=PADX, sticky=tk.W)
self.cmdr_text = nb.Label(credframe, text=_('None')) # No hotkey/shortcut currently defined
self.cmdr_text.grid(row=10, column=1, padx=PADX, pady=PADY, sticky=tk.W)
self.username = nb.Entry(credframe) self.username = nb.Entry(credframe)
self.username.insert(0, config.get('username') or '') self.username.grid(row=11, column=1, padx=PADX, pady=PADY, sticky=tk.EW)
self.username.grid(row=10, column=1, padx=PADX, pady=PADY, sticky=tk.EW) if not monitor.is_beta and monitor.cmdr:
self.username.focus_set() self.username.focus_set()
self.password = nb.Entry(credframe, show=u'') self.password = nb.Entry(credframe, show=u'')
self.password.insert(0, config.get('password') or '') self.password.grid(row=12, column=1, padx=PADX, pady=PADY, sticky=tk.EW)
self.password.grid(row=11, column=1, padx=PADX, pady=PADY, sticky=tk.EW)
nb.Label(credframe).grid(sticky=tk.W) # big spacer nb.Label(credframe).grid(sticky=tk.W) # big spacer
nb.Label(credframe, text=_('Privacy')).grid(padx=PADX, sticky=tk.W) # Section heading in settings nb.Label(credframe, text=_('Privacy')).grid(padx=PADX, sticky=tk.W) # Section heading in settings
@ -125,13 +132,17 @@ class PreferencesDialog(tk.Toplevel):
output = config.getint('output') or (config.OUT_MKT_EDDN | config.OUT_SYS_EDDN | config.OUT_SHIP) # default settings output = config.getint('output') or (config.OUT_MKT_EDDN | config.OUT_SYS_EDDN | config.OUT_SHIP) # default settings
nb.Label(outframe, text=_('Please choose what data to save')).grid(columnspan=2, padx=PADX, sticky=tk.W) self.out_label = nb.Label(outframe, text=_('Please choose what data to save'))
self.out_label.grid(columnspan=2, padx=PADX, sticky=tk.W)
self.out_csv = tk.IntVar(value = (output & config.OUT_MKT_CSV ) and 1) self.out_csv = tk.IntVar(value = (output & config.OUT_MKT_CSV ) and 1)
nb.Checkbutton(outframe, text=_('Market data in CSV format file'), variable=self.out_csv, command=self.outvarchanged).grid(columnspan=2, padx=BUTTONX, sticky=tk.W) self.out_csv_button = nb.Checkbutton(outframe, text=_('Market data in CSV format file'), variable=self.out_csv, command=self.outvarchanged)
self.out_csv_button.grid(columnspan=2, padx=BUTTONX, sticky=tk.W)
self.out_td = tk.IntVar(value = (output & config.OUT_MKT_TD ) and 1) self.out_td = tk.IntVar(value = (output & config.OUT_MKT_TD ) and 1)
nb.Checkbutton(outframe, text=_('Market data in Trade Dangerous format file'), variable=self.out_td, command=self.outvarchanged).grid(columnspan=2, padx=BUTTONX, sticky=tk.W) self.out_td_button = nb.Checkbutton(outframe, text=_('Market data in Trade Dangerous format file'), variable=self.out_td, command=self.outvarchanged)
self.out_td_button.grid(columnspan=2, padx=BUTTONX, sticky=tk.W)
self.out_ship= tk.IntVar(value = (output & (config.OUT_SHIP|config.OUT_SHIP_EDS|config.OUT_SHIP_CORIOLIS) and 1)) self.out_ship= tk.IntVar(value = (output & (config.OUT_SHIP|config.OUT_SHIP_EDS|config.OUT_SHIP_CORIOLIS) and 1))
nb.Checkbutton(outframe, text=_('Ship loadout'), variable=self.out_ship, command=self.outvarchanged).grid(columnspan=2, padx=BUTTONX, pady=(5,0), sticky=tk.W) # Output setting self.out_ship_button = nb.Checkbutton(outframe, text=_('Ship loadout'), variable=self.out_ship, command=self.outvarchanged) # Output setting
self.out_ship_button.grid(columnspan=2, padx=BUTTONX, pady=(5,0), sticky=tk.W)
self.out_auto = tk.IntVar(value = 0 if output & config.OUT_MKT_MANUAL else 1) # inverted self.out_auto = tk.IntVar(value = 0 if output & config.OUT_MKT_MANUAL else 1) # inverted
self.out_auto_button = nb.Checkbutton(outframe, text=_('Automatically update on docking'), variable=self.out_auto, command=self.outvarchanged) # Output setting self.out_auto_button = nb.Checkbutton(outframe, text=_('Automatically update on docking'), variable=self.out_auto, command=self.outvarchanged) # Output setting
self.out_auto_button.grid(columnspan=2, padx=BUTTONX, pady=(5,0), sticky=tk.W) self.out_auto_button.grid(columnspan=2, padx=BUTTONX, pady=(5,0), sticky=tk.W)
@ -155,7 +166,8 @@ class PreferencesDialog(tk.Toplevel):
HyperlinkLabel(eddnframe, text='Elite Dangerous Data Network', background=nb.Label().cget('background'), url='https://github.com/jamesremuscat/EDDN/wiki', underline=True).grid(padx=PADX, sticky=tk.W) # Don't translate HyperlinkLabel(eddnframe, text='Elite Dangerous Data Network', background=nb.Label().cget('background'), url='https://github.com/jamesremuscat/EDDN/wiki', underline=True).grid(padx=PADX, sticky=tk.W) # Don't translate
self.eddn_station= tk.IntVar(value = (output & config.OUT_MKT_EDDN) and 1) self.eddn_station= tk.IntVar(value = (output & config.OUT_MKT_EDDN) and 1)
nb.Checkbutton(eddnframe, text=_('Send station data to the Elite Dangerous Data Network'), variable=self.eddn_station, command=self.outvarchanged).grid(padx=BUTTONX, pady=(5,0), sticky=tk.W) # Output setting self.eddn_station_button = nb.Checkbutton(eddnframe, text=_('Send station data to the Elite Dangerous Data Network'), variable=self.eddn_station, command=self.outvarchanged) # Output setting
self.eddn_station_button.grid(padx=BUTTONX, pady=(5,0), sticky=tk.W)
self.eddn_auto_button = nb.Checkbutton(eddnframe, text=_('Automatically update on docking'), variable=self.out_auto, command=self.outvarchanged) # Output setting self.eddn_auto_button = nb.Checkbutton(eddnframe, text=_('Automatically update on docking'), variable=self.out_auto, command=self.outvarchanged) # Output setting
self.eddn_auto_button.grid(padx=BUTTONX, sticky=tk.W) self.eddn_auto_button.grid(padx=BUTTONX, sticky=tk.W)
self.eddn_system = tk.IntVar(value = (output & config.OUT_SYS_EDDN) and 1) self.eddn_system = tk.IntVar(value = (output & config.OUT_SYS_EDDN) and 1)
@ -180,17 +192,20 @@ class PreferencesDialog(tk.Toplevel):
self.edsm_label = HyperlinkLabel(edsmframe, text=_('Elite Dangerous Star Map credentials'), background=nb.Label().cget('background'), url='https://www.edsm.net/settings/api', underline=True) # Section heading in settings self.edsm_label = HyperlinkLabel(edsmframe, text=_('Elite Dangerous Star Map credentials'), background=nb.Label().cget('background'), url='https://www.edsm.net/settings/api', underline=True) # Section heading in settings
self.edsm_label.grid(columnspan=2, padx=PADX, sticky=tk.W) self.edsm_label.grid(columnspan=2, padx=PADX, sticky=tk.W)
self.edsm_cmdr_label = nb.Label(edsmframe, text=_('Commander Name')) # EDSM setting self.edsm_cmdr_label = nb.Label(edsmframe, text=_('Cmdr')) # Main window
self.edsm_cmdr_label.grid(row=10, padx=PADX, sticky=tk.W) self.edsm_cmdr_label.grid(row=10, padx=PADX, sticky=tk.W)
self.edsm_cmdr = nb.Entry(edsmframe) self.edsm_cmdr_text = nb.Label(edsmframe, text=_('None')) # No hotkey/shortcut currently defined
self.edsm_cmdr.insert(0, config.get('edsm_cmdrname') or '') self.edsm_cmdr_text.grid(row=10, column=1, padx=PADX, pady=PADY, sticky=tk.W)
self.edsm_cmdr.grid(row=10, column=1, padx=PADX, pady=PADY, sticky=tk.EW)
self.edsm_user_label = nb.Label(edsmframe, text=_('Commander Name')) # EDSM setting
self.edsm_user_label.grid(row=11, padx=PADX, sticky=tk.W)
self.edsm_user = nb.Entry(edsmframe)
self.edsm_user.grid(row=11, column=1, padx=PADX, pady=PADY, sticky=tk.EW)
self.edsm_apikey_label = nb.Label(edsmframe, text=_('API Key')) # EDSM setting self.edsm_apikey_label = nb.Label(edsmframe, text=_('API Key')) # EDSM setting
self.edsm_apikey_label.grid(row=11, padx=PADX, sticky=tk.W) self.edsm_apikey_label.grid(row=12, padx=PADX, sticky=tk.W)
self.edsm_apikey = nb.Entry(edsmframe) self.edsm_apikey = nb.Entry(edsmframe)
self.edsm_apikey.insert(0, config.get('edsm_apikey') or '') self.edsm_apikey.grid(row=12, column=1, padx=PADX, pady=PADY, sticky=tk.EW)
self.edsm_apikey.grid(row=11, column=1, padx=PADX, pady=PADY, sticky=tk.EW)
notebook.add(edsmframe, text='EDSM') # Not translated notebook.add(edsmframe, text='EDSM') # Not translated
@ -321,30 +336,57 @@ class PreferencesDialog(tk.Toplevel):
0x10000, None, position): 0x10000, None, position):
self.geometry("+%d+%d" % (position.left, position.top)) self.geometry("+%d+%d" % (position.left, position.top))
def outvarchanged(self): def outvarchanged(self, event=None):
self.cmdr_text['state'] = self.edsm_cmdr_text['state'] = tk.NORMAL # must be writable to update
self.cmdr_text['text'] = self.edsm_cmdr_text['text'] = monitor.cmdr and monitor.is_beta and ('%s [Beta]' % monitor.cmdr) or monitor.cmdr or _('None') # No hotkey/shortcut currently defined
if self.cmdr != monitor.cmdr:
# Cmdr has changed - update settings
if not monitor.is_beta:
migrate(monitor.cmdr) # migration from <= 2.25
if monitor.cmdr and config.get('cmdrs') and monitor.cmdr in config.get('cmdrs'):
config_idx = config.get('cmdrs').index(monitor.cmdr)
else:
config_idx = None
self.username['state'] = tk.NORMAL
self.username.delete(0, tk.END)
self.username.insert(0, config_idx is not None and config.get('fdev_usernames')[config_idx] or '')
self.password['state'] = tk.NORMAL
self.password.delete(0, tk.END)
self.password.insert(0, config_idx is not None and config.get('fdev_passwords')[config_idx] or '')
self.edsm_user['state'] = tk.NORMAL
self.edsm_user.delete(0, tk.END)
self.edsm_user.insert(0, config_idx is not None and config.get('edsm_usernames')[config_idx] or '')
self.edsm_apikey['state'] = tk.NORMAL
self.edsm_apikey.delete(0, tk.END)
self.edsm_apikey.insert(0, config_idx is not None and config.get('edsm_apikeys')[config_idx] or '')
self.cmdr = monitor.cmdr
cmdr_state = not monitor.is_beta and monitor.cmdr and tk.NORMAL or tk.DISABLED
self.cred_label['state'] = self.cmdr_label['state'] = self.username_label['state'] = self.password_label['state'] = cmdr_state
self.cmdr_text['state'] = self.username['state'] = self.password['state'] = cmdr_state
self.displaypath(self.outdir, self.outdir_entry) self.displaypath(self.outdir, self.outdir_entry)
self.displaypath(self.logdir, self.logdir_entry) self.displaypath(self.logdir, self.logdir_entry)
logdir = self.logdir.get() logdir = self.logdir.get()
logvalid = logdir and exists(logdir) logvalid = logdir and exists(logdir)
local = self.out_td.get() or self.out_csv.get() or self.out_ship.get() self.out_label['state'] = self.out_csv_button['state'] = self.out_td_button['state'] = self.out_ship_button['state'] = not monitor.is_beta and tk.NORMAL or tk.DISABLED
self.out_auto_button['state'] = local and logvalid and not monitor.is_beta and tk.NORMAL or tk.DISABLED local = not monitor.is_beta and (self.out_td.get() or self.out_csv.get() or self.out_ship.get())
self.out_auto_button['state'] = local and logvalid and tk.NORMAL or tk.DISABLED
self.outdir_label['state'] = local and tk.NORMAL or tk.DISABLED self.outdir_label['state'] = local and tk.NORMAL or tk.DISABLED
self.outbutton['state'] = local and tk.NORMAL or tk.DISABLED self.outbutton['state'] = local and tk.NORMAL or tk.DISABLED
self.outdir_entry['state'] = local and 'readonly' or tk.DISABLED self.outdir_entry['state'] = local and 'readonly' or tk.DISABLED
self.eddn_station_button['state'] = not monitor.is_beta and tk.NORMAL or tk.DISABLED
self.eddn_auto_button['state'] = self.eddn_station.get() and logvalid and not monitor.is_beta and tk.NORMAL or tk.DISABLED self.eddn_auto_button['state'] = self.eddn_station.get() and logvalid and not monitor.is_beta and tk.NORMAL or tk.DISABLED
self.eddn_system_button['state']= logvalid and tk.NORMAL or tk.DISABLED self.eddn_system_button['state']= logvalid and tk.NORMAL or tk.DISABLED
self.eddn_delay_button['state'] = logvalid and eddn.replayfile and self.eddn_system.get() and tk.NORMAL or tk.DISABLED self.eddn_delay_button['state'] = logvalid and eddn.replayfile and self.eddn_system.get() and tk.NORMAL or tk.DISABLED
self.edsm_log_button['state'] = logvalid and tk.NORMAL or tk.DISABLED self.edsm_log_button['state'] = logvalid and not monitor.is_beta and tk.NORMAL or tk.DISABLED
edsm_state = logvalid and self.edsm_log.get() and tk.NORMAL or tk.DISABLED edsm_state = logvalid and monitor.cmdr and not monitor.is_beta and self.edsm_log.get() and tk.NORMAL or tk.DISABLED
self.edsm_label['state'] = edsm_state self.edsm_label['state'] = self.edsm_cmdr_label['state'] = self.edsm_user_label['state'] = self.edsm_apikey_label['state'] = edsm_state
self.edsm_cmdr_label['state'] = edsm_state self.edsm_cmdr_text['state'] = self.edsm_user['state'] = self.edsm_apikey['state'] = edsm_state
self.edsm_apikey_label['state'] = edsm_state
self.edsm_cmdr['state'] = edsm_state
self.edsm_apikey['state'] = edsm_state
def filebrowse(self, title, pathvar): def filebrowse(self, title, pathvar):
if platform != 'win32': if platform != 'win32':
@ -474,9 +516,20 @@ class PreferencesDialog(tk.Toplevel):
def apply(self): def apply(self):
credentials = (config.get('username'), config.get('password')) if self.cmdr and not monitor.is_beta:
config.set('username', self.username.get().strip()) if not config.get('cmdrs'):
config.set('password', self.password.get().strip()) config.set('cmdrs', [self.cmdr])
config.set('fdev_usernames', [self.username.get().strip()])
config.set('fdev_passwords', [self.password.get().strip()])
config.set('edsm_usernames', [self.edsm_user.get().strip()])
config.set('edsm_apikeys', [self.edsm_apikey.get().strip()])
else:
idx = config.get('cmdrs').index(self.cmdr) if self.cmdr in config.get('cmdrs') else -1
_putfirst('cmdrs', idx, self.cmdr)
_putfirst('fdev_usernames', idx, self.username.get().strip())
_putfirst('fdev_passwords', idx, self.password.get().strip())
_putfirst('edsm_usernames', idx, self.edsm_user.get().strip())
_putfirst('edsm_apikeys', idx, self.edsm_apikey.get().strip())
config.set('output', config.set('output',
(self.out_td.get() and config.OUT_MKT_TD) + (self.out_td.get() and config.OUT_MKT_TD) +
@ -489,9 +542,6 @@ class PreferencesDialog(tk.Toplevel):
(self.edsm_log.get() and config.OUT_SYS_EDSM)) (self.edsm_log.get() and config.OUT_SYS_EDSM))
config.set('outdir', self.outdir.get().startswith('~') and join(config.home, self.outdir.get()[2:]) or self.outdir.get()) config.set('outdir', self.outdir.get().startswith('~') and join(config.home, self.outdir.get()[2:]) or self.outdir.get())
config.set('edsm_cmdrname', self.edsm_cmdr.get().strip())
config.set('edsm_apikey', self.edsm_apikey.get().strip())
logdir = self.logdir.get() logdir = self.logdir.get()
if config.default_journal_dir and logdir.lower() == config.default_journal_dir.lower(): if config.default_journal_dir and logdir.lower() == config.default_journal_dir.lower():
config.set('journaldir', '') # default location config.set('journaldir', '') # default location
@ -621,3 +671,35 @@ class AuthenticationDialog(tk.Toplevel):
self.parent.wm_attributes('-topmost', config.getint('always_ontop') and 1 or 0) self.parent.wm_attributes('-topmost', config.getint('always_ontop') and 1 or 0)
self.destroy() self.destroy()
if self.callback: self.callback(None) if self.callback: self.callback(None)
# migration from <= 2.25. Assumes current Cmdr corresponds to the saved credentials
def migrate(current_cmdr):
if current_cmdr and not config.get('cmdrs') and config.get('username') and config.get('password'):
config.set('cmdrs', [current_cmdr])
config.set('fdev_usernames', [config.get('username')])
config.set('fdev_passwords', [config.get('password')])
config.set('edsm_usernames', [config.get('edsm_cmdrname') or ''])
config.set('edsm_apikeys', [config.get('edsm_apikey') or ''])
# XXX to be done for release
# config.delete('username')
# config.delete('password')
# config.delete('edsm_cmdrname')
# config.delete('edsm_apikey')
# Put current Cmdr first in the lists
def make_current(current_cmdr):
if current_cmdr and config.get('cmdrs') and current_cmdr in config.get('cmdrs'):
idx = config.get('cmdrs').index(current_cmdr)
_putfirst('cmdrs', idx)
_putfirst('fdev_usernames', idx)
_putfirst('fdev_passwords', idx)
_putfirst('edsm_usernames', idx)
_putfirst('edsm_apikeys', idx)
def _putfirst(setting, config_idx, new_value=None):
assert config_idx>=0 or new_value is not None, (setting, config_idx, new_value)
values = config.get(setting)
values.insert(0, new_value if config_idx<0 else values.pop(config_idx))
if new_value is not None:
values[0] = new_value
config.set(setting, values)

View File

@ -11,6 +11,7 @@ import myNotebook as nb
import companion import companion
from companion import ship_map from companion import ship_map
from monitor import monitor
import prefs import prefs
if platform=='win32': if platform=='win32':
@ -175,6 +176,9 @@ class StatsDialog():
self.showstats() self.showstats()
def showstats(self): def showstats(self):
if not monitor.cmdr or monitor.is_beta:
return # In Beta - do nothing
self.status['text'] = _('Fetching data...') self.status['text'] = _('Fetching data...')
self.parent.update_idletasks() self.parent.update_idletasks()