From a67eb982d84acd8627738d148ff6d8e7346293db Mon Sep 17 00:00:00 2001 From: Jonathan Harris Date: Sat, 21 Jan 2017 21:40:15 +0000 Subject: [PATCH] Multi-account support Fixes #121 --- EDMC.py | 5 +- EDMarketConnector.py | 69 +++++++++++-------- README.md | 3 + companion.py | 44 ++++++------ edsm.py | 6 +- monitor.py | 6 +- prefs.py | 156 +++++++++++++++++++++++++++++++++---------- stats.py | 4 ++ 8 files changed, 201 insertions(+), 92 deletions(-) diff --git a/EDMC.py b/EDMC.py index 46e34c0c..d720c809 100755 --- a/EDMC.py +++ b/EDMC.py @@ -82,7 +82,10 @@ try: config.set('querytime', getmtime(args.j)) else: session = companion.Session() - session.login(config.get('username'), config.get('password')) + 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')) querytime = int(time()) data = session.query() config.set('querytime', querytime) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 552a5e5b..c57ef31b 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -236,8 +236,6 @@ class AppWindow: theme.register(self.theme_close) theme.register_alternate((self.menubar, self.theme_menubar), {'row':0, 'columnspan':2, 'sticky':tk.NSEW}) - self.set_labels() - # update geometry if config.get('geometry'): match = re.match('\+([\-\d]+)\+([\-\d]+)', config.get('geometry')) @@ -274,17 +272,25 @@ class AppWindow: # Load updater after UI creation (for WinSparkle) import update 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 - if not config.get('username') or not config.get('password'): - prefs.PreferencesDialog(self.w, self.postprefs) - else: - self.login() + self.postprefs() # Companion login happens in callback from monitor + + # 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 # callback after the Preferences dialog is applied def postprefs(self): 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 def set_labels(self): @@ -321,11 +327,15 @@ class AppWindow: self.edit_menu.entryconfigure(0, label=_('Copy')) # As in Copy and Paste def login(self): - self.status['text'] = _('Logging in...') + if not self.status['text']: + self.status['text'] = _('Logging in...') self.button['state'] = self.theme_button['state'] = tk.DISABLED self.w.update_idletasks() 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'] = '' except companion.VerificationRequired: return prefs.AuthenticationDialog(self.w, partial(self.verify, self.login)) @@ -334,21 +344,6 @@ class AppWindow: except Exception as e: if __debug__: print_exc() 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() # callback after verification code @@ -368,7 +363,7 @@ class AppWindow: auto_update = not event 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 if not retrying: @@ -478,7 +473,7 @@ class AppWindow: self.status['text'] = '' # 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: if data['commander'].get('credits') is not None: monitor.credits = (data['commander']['credits'], data['commander'].get('debt', 0)) @@ -568,13 +563,25 @@ class AppWindow: self.edsm.link(monitor.system) 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 if config.getint('output') & config.OUT_SYS_EDSM and not monitor.is_beta: self.status['text'] = _('Sending data to EDSM...') self.w.update_idletasks() try: # 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) # 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.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: return # Startup or in CQC @@ -688,7 +699,7 @@ class AppWindow: 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 self.status['text'] = _('Fetching data...') diff --git a/README.md b/README.md index 48766df8..4321e30e 100644 --- a/README.md +++ b/README.md @@ -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). +### 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 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. diff --git a/companion.py b/companion.py index 11b2f89d..0a4cd183 100644 --- a/companion.py +++ b/companion.py @@ -1,9 +1,10 @@ import requests from collections import defaultdict from cookielib import LWPCookieJar +import hashlib import numbers import os -from os.path import dirname, join +from os.path import dirname, isfile, join import sys from sys import platform import time @@ -178,6 +179,7 @@ class Session: def __init__(self): self.state = Session.STATE_INIT self.credentials = None + self.session = None # yuck suppress InsecurePlatformWarning under Python < 2.7.9 which lacks SNI support try: @@ -189,14 +191,6 @@ class Session: if platform=='win32' and getattr(sys, 'frozen', False): 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): if (not username or not password): if not self.credentials: @@ -208,8 +202,20 @@ class Session: if self.credentials == credentials and self.state == Session.STATE_OK: 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.state = Session.STATE_INIT try: @@ -237,7 +243,7 @@ class Session: 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 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() def query(self): @@ -270,16 +276,14 @@ class Session: return data - def save(self): - self.session.cookies.save() - def close(self): self.state = Session.STATE_NONE - try: - self.session.cookies.save() - self.session.close() - except: - pass + if self.session: + try: + self.session.cookies.save() + self.session.close() + except: + if __debug__: print_exc() self.session = None def dump(self, r): diff --git a/edsm.py b/edsm.py index 3a881fe2..79f6a1f8 100644 --- a/edsm.py +++ b/edsm.py @@ -7,6 +7,7 @@ import urllib2 import Tkinter as tk from config import appname, applongname, appversion, config +from monitor import monitor if __debug__: from traceback import print_exc @@ -79,10 +80,11 @@ class EDSM: # Call an EDSM endpoint with args (which should be quoted) def call(self, endpoint, args, check_msgnum=True): try: + idx = config.get('cmdrs').index(monitor.cmdr) url = 'https://www.edsm.net/%s?commanderName=%s&apiKey=%s&fromSoftware=%s&fromSoftwareVersion=%s' % ( endpoint, - urllib2.quote(config.get('edsm_cmdrname').encode('utf-8')), - urllib2.quote(config.get('edsm_apikey')), + urllib2.quote(config.get('edsm_usernames')[idx].encode('utf-8')), + urllib2.quote(config.get('edsm_apikeys')[idx]), urllib2.quote(applongname), urllib2.quote(appversion), ) + args diff --git a/monitor.py b/monitor.py index 41f0b305..b7206ba1 100644 --- a/monitor.py +++ b/monitor.py @@ -106,7 +106,7 @@ class EDLogs(FileSystemEventHandler): # 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 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 except: self.logfile = None @@ -129,6 +129,8 @@ class EDLogs(FileSystemEventHandler): print '%s "%s"' % (polling and 'Polling' or 'Monitoring', self.currentdir) print 'Start logfile "%s"' % self.logfile + self.event_queue.append(None) # Generate null event to signal (re)start + if not self.running(): self.thread = threading.Thread(target = self.worker, name = 'Journal worker') self.thread.daemon = True @@ -181,8 +183,6 @@ class EDLogs(FileSystemEventHandler): except: if __debug__: print 'Invalid journal entry "%s"' % repr(line) - self.event_queue.append(None) # Generate null event to signal start - self.root.event_generate('<>', when="tail") else: loghandle = None diff --git a/prefs.py b/prefs.py index 51db8c16..9510b21a 100644 --- a/prefs.py +++ b/prefs.py @@ -80,12 +80,13 @@ class PreferencesDialog(tk.Toplevel): parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility') self.resizable(tk.FALSE, tk.FALSE) - style = ttk.Style() + self.cmdr = None # Note if Cmdr changes in the Journal frame = ttk.Frame(self) frame.grid(sticky=tk.NSEW) notebook = nb.Notebook(frame) + notebook.bind('<>', self.outvarchanged) # Recompute on tab change PADX = 10 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 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 - nb.Label(credframe, text=_('Username (Email)')).grid(row=10, padx=PADX, sticky=tk.W) # Use same text as E:D Launcher's login dialog - nb.Label(credframe, text=_('Password')).grid(row=11, padx=PADX, 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 + self.cred_label.grid(padx=PADX, columnspan=2, sticky=tk.W) + 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.insert(0, config.get('username') or '') - self.username.grid(row=10, column=1, padx=PADX, pady=PADY, sticky=tk.EW) - self.username.focus_set() + self.username.grid(row=11, column=1, padx=PADX, pady=PADY, sticky=tk.EW) + if not monitor.is_beta and monitor.cmdr: + self.username.focus_set() self.password = nb.Entry(credframe, show=u'•') - self.password.insert(0, config.get('password') or '') - self.password.grid(row=11, column=1, padx=PADX, pady=PADY, sticky=tk.EW) + self.password.grid(row=12, column=1, padx=PADX, pady=PADY, sticky=tk.EW) nb.Label(credframe).grid(sticky=tk.W) # big spacer 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 - 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) - 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) - 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)) - 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_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) @@ -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 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.grid(padx=BUTTONX, sticky=tk.W) 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.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 = nb.Entry(edsmframe) - self.edsm_cmdr.insert(0, config.get('edsm_cmdrname') or '') - self.edsm_cmdr.grid(row=10, column=1, padx=PADX, pady=PADY, sticky=tk.EW) + self.edsm_cmdr_text = nb.Label(edsmframe, text=_('None')) # No hotkey/shortcut currently defined + self.edsm_cmdr_text.grid(row=10, column=1, padx=PADX, pady=PADY, sticky=tk.W) + + 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.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.insert(0, config.get('edsm_apikey') or '') - self.edsm_apikey.grid(row=11, column=1, padx=PADX, pady=PADY, sticky=tk.EW) + self.edsm_apikey.grid(row=12, column=1, padx=PADX, pady=PADY, sticky=tk.EW) notebook.add(edsmframe, text='EDSM') # Not translated @@ -321,30 +336,57 @@ class PreferencesDialog(tk.Toplevel): 0x10000, None, position): 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.logdir, self.logdir_entry) logdir = self.logdir.get() logvalid = logdir and exists(logdir) - local = self.out_td.get() or self.out_csv.get() or self.out_ship.get() - self.out_auto_button['state'] = local and logvalid and not monitor.is_beta and tk.NORMAL or tk.DISABLED + 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 + 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.outbutton['state'] = local and tk.NORMAL 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_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.edsm_log_button['state'] = logvalid and tk.NORMAL or tk.DISABLED - edsm_state = logvalid and self.edsm_log.get() and tk.NORMAL or tk.DISABLED - self.edsm_label['state'] = edsm_state - self.edsm_cmdr_label['state'] = edsm_state - self.edsm_apikey_label['state'] = edsm_state - self.edsm_cmdr['state'] = edsm_state - self.edsm_apikey['state'] = edsm_state + self.edsm_log_button['state'] = logvalid and not monitor.is_beta 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'] = self.edsm_cmdr_label['state'] = self.edsm_user_label['state'] = self.edsm_apikey_label['state'] = edsm_state + self.edsm_cmdr_text['state'] = self.edsm_user['state'] = self.edsm_apikey['state'] = edsm_state def filebrowse(self, title, pathvar): if platform != 'win32': @@ -474,9 +516,20 @@ class PreferencesDialog(tk.Toplevel): def apply(self): - credentials = (config.get('username'), config.get('password')) - config.set('username', self.username.get().strip()) - config.set('password', self.password.get().strip()) + if self.cmdr and not monitor.is_beta: + if not config.get('cmdrs'): + 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', (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)) 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() if config.default_journal_dir and logdir.lower() == config.default_journal_dir.lower(): 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.destroy() 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) diff --git a/stats.py b/stats.py index 998b8afc..1bf23bb4 100644 --- a/stats.py +++ b/stats.py @@ -11,6 +11,7 @@ import myNotebook as nb import companion from companion import ship_map +from monitor import monitor import prefs if platform=='win32': @@ -175,6 +176,9 @@ class StatsDialog(): self.showstats() def showstats(self): + if not monitor.cmdr or monitor.is_beta: + return # In Beta - do nothing + self.status['text'] = _('Fetching data...') self.parent.update_idletasks()