mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2025-04-14 16:27:13 +03:00
Merge branch 'multi_account'
This commit is contained in:
commit
0022f78e62
6
EDMC.py
6
EDMC.py
@ -82,7 +82,11 @@ try:
|
||||
config.set('querytime', getmtime(args.j))
|
||||
else:
|
||||
session = companion.Session()
|
||||
session.login(config.get('username'), config.get('password'))
|
||||
if config.get('cmdrs'):
|
||||
username = config.get('fdev_usernames')[0]
|
||||
session.login(username, config.get_password(username))
|
||||
else: # <= 2.25 not yet migrated
|
||||
session.login(config.get('username'), config.get('password'))
|
||||
querytime = int(time())
|
||||
data = session.query()
|
||||
config.set('querytime', querytime)
|
||||
|
@ -6,6 +6,7 @@ from sys import platform
|
||||
from collections import OrderedDict
|
||||
from functools import partial
|
||||
import json
|
||||
import keyring
|
||||
from os import chdir, mkdir
|
||||
from os.path import dirname, expanduser, isdir, join
|
||||
import re
|
||||
@ -28,8 +29,9 @@ if __debug__:
|
||||
signal.signal(signal.SIGTERM, lambda sig, frame: pdb.Pdb().set_trace(frame))
|
||||
|
||||
from config import appname, applongname, config
|
||||
if platform == 'win32' and getattr(sys, 'frozen', False):
|
||||
chdir(dirname(sys.path[0]))
|
||||
if getattr(sys, 'frozen', False):
|
||||
if platform == 'win32':
|
||||
chdir(dirname(sys.path[0]))
|
||||
# By default py2exe tries to write log to dirname(sys.executable) which fails when installed
|
||||
import tempfile
|
||||
sys.stdout = sys.stderr = open(join(tempfile.gettempdir(), '%s.log' % appname), 'wt', 0) # unbuffered
|
||||
@ -239,8 +241,6 @@ class AppWindow:
|
||||
tk.Label(self.blank_menubar).grid()
|
||||
theme.register_alternate((self.menubar, self.theme_menubar, self.blank_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'))
|
||||
@ -281,17 +281,42 @@ 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()
|
||||
try:
|
||||
config.get_password('') # Prod SecureStorage on Linux to initialise
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
# Migration from <= 2.25
|
||||
if not config.get('cmdrs') and config.get('username') and config.get('password'):
|
||||
try:
|
||||
self.session.login(config.get('username'), config.get('password'))
|
||||
data = self.session.query()
|
||||
prefs.migrate(data['commander']['name'])
|
||||
except:
|
||||
if __debug__: print_exc()
|
||||
|
||||
self.postprefs() # Companion login happens in callback from monitor
|
||||
|
||||
if keyring.get_keyring().priority < 1:
|
||||
self.status['text'] = 'Warning: Storing passwords as text' # Shouldn't happen unless no secure storage on Linux
|
||||
|
||||
# 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):
|
||||
@ -328,11 +353,16 @@ 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 not config.get('cmdrs') or monitor.cmdr not in config.get('cmdrs'):
|
||||
raise companion.CredentialsError()
|
||||
idx = config.get('cmdrs').index(monitor.cmdr)
|
||||
username = config.get('fdev_usernames')[idx]
|
||||
self.session.login(username, config.get_password(username))
|
||||
self.status['text'] = ''
|
||||
except companion.VerificationRequired:
|
||||
return prefs.AuthenticationDialog(self.w, partial(self.verify, self.login))
|
||||
@ -341,21 +371,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
|
||||
@ -375,7 +390,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:
|
||||
@ -485,7 +500,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))
|
||||
@ -576,12 +591,12 @@ class AppWindow:
|
||||
self.w.update_idletasks()
|
||||
|
||||
# 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 and config.get('cmdrs') and monitor.cmdr in config.get('cmdrs') and config.get('edsm_usernames')[config.get('cmdrs').index(monitor.cmdr)]:
|
||||
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)
|
||||
@ -616,6 +631,18 @@ 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, 'NewCommander', 'LoadGame'] and monitor.cmdr and not monitor.is_beta:
|
||||
if config.get('cmdrs') and monitor.cmdr in config.get('cmdrs'):
|
||||
prefs.make_current(monitor.cmdr)
|
||||
self.login()
|
||||
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 failed migration
|
||||
|
||||
if not entry['event'] or not monitor.mode:
|
||||
return # Startup or in CQC
|
||||
|
||||
@ -695,7 +722,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...')
|
||||
@ -839,14 +866,40 @@ class AppWindow:
|
||||
# Run the app
|
||||
if __name__ == "__main__":
|
||||
|
||||
# Ensure only one copy of the app is running. OSX does this automatically. Linux TODO.
|
||||
# Ensure only one copy of the app is running under this user account. OSX does this automatically. Linux TODO.
|
||||
if platform == 'win32':
|
||||
import ctypes
|
||||
h = ctypes.windll.user32.FindWindowW(u'TkTopLevel', unicode(applongname))
|
||||
if h:
|
||||
ctypes.windll.user32.ShowWindow(h, 9) # SW_RESTORE
|
||||
ctypes.windll.user32.SetForegroundWindow(h) # Probably not necessary
|
||||
sys.exit(0)
|
||||
from ctypes.wintypes import *
|
||||
EnumWindows = ctypes.windll.user32.EnumWindows
|
||||
EnumWindowsProc = ctypes.WINFUNCTYPE(BOOL, HWND, LPARAM)
|
||||
GetClassName = ctypes.windll.user32.GetClassNameW
|
||||
GetClassName.argtypes = [HWND, LPWSTR, ctypes.c_int]
|
||||
GetWindowText = ctypes.windll.user32.GetWindowTextW
|
||||
GetWindowText.argtypes = [HWND, LPWSTR, ctypes.c_int]
|
||||
GetWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW
|
||||
GetProcessHandleFromHwnd = ctypes.windll.oleacc.GetProcessHandleFromHwnd
|
||||
SetForegroundWindow = ctypes.windll.user32.SetForegroundWindow
|
||||
ShowWindow = ctypes.windll.user32.ShowWindow
|
||||
|
||||
def WindowTitle(h):
|
||||
if h:
|
||||
l = GetWindowTextLength(h) + 1
|
||||
buf = ctypes.create_unicode_buffer(l)
|
||||
if GetWindowText(h, buf, l):
|
||||
return buf.value
|
||||
return None
|
||||
|
||||
def enumwindowsproc(hWnd, lParam):
|
||||
# class name limited to 256 - https://msdn.microsoft.com/en-us/library/windows/desktop/ms633576
|
||||
cls = ctypes.create_unicode_buffer(257)
|
||||
if GetClassName(hWnd, cls, 257) and cls.value == 'TkTopLevel' and WindowTitle(hWnd) == applongname and GetProcessHandleFromHwnd(hWnd):
|
||||
# If GetProcessHandleFromHwnd succeeds then the app is already running as this user
|
||||
ShowWindow(hWnd, 9) # SW_RESTORE
|
||||
SetForegroundWindow(hWnd)
|
||||
sys.exit(0)
|
||||
return True
|
||||
|
||||
EnumWindows(EnumWindowsProc(enumwindowsproc), 0)
|
||||
|
||||
root = tk.Tk()
|
||||
app = AppWindow(root)
|
||||
|
15
README.md
15
README.md
@ -145,6 +145,14 @@ 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 or in Beta. You will be able to edit these values once you've entered the (non-Beta) game.
|
||||
|
||||
### I run two instances of E:D simultaneously, but I can't run two instances of EDMC
|
||||
EDMC supports this scenario if you run the second instance of E:D in a *different* user account - e.g. using `runas` on Windows. Run the second instance of EDMC in the same user account as the second instance of E:D.
|
||||
|
||||
EDMC doesn't support running two instances of E:D in the *same* user account. EDMC will only respond to the instance of E:D that you ran last.
|
||||
|
||||
### 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.
|
||||
|
||||
@ -158,17 +166,17 @@ Download and extract the source code of the [latest release](https://github.com/
|
||||
|
||||
Mac:
|
||||
|
||||
* Requires the Python “requests” and “watchdog” modules, plus an up-to-date “py2app” module if you also want to package the app - install these with `easy_install -U requests watchdog py2app` .
|
||||
* Requires the Python “keyring”, “requests” and “watchdog” modules, plus an up-to-date “py2app” module if you also want to package the app - install these with `easy_install -U keyring requests watchdog py2app` .
|
||||
* Run with `./EDMarketConnector.py` .
|
||||
|
||||
Windows:
|
||||
|
||||
* Requires Python2.7 and the Python “requests” and “watchdog” modules.
|
||||
* Requires Python2.7 and the Python “keyring”, “requests” and “watchdog” modules, plus “py2exe” 0.6 if you also want to package the app.
|
||||
* Run with `EDMarketConnector.py` .
|
||||
|
||||
Linux:
|
||||
|
||||
* Requires the Python “imaging-tk”, “iniparse” and “requests” modules. On Debian-based systems install these with `sudo apt-get install python-imaging-tk python-iniparse python-requests` .
|
||||
* Requires the Python “imaging-tk”, “iniparse”, “keyring” and “requests” modules. On Debian-based systems install these with `sudo apt-get install python-imaging-tk python-iniparse python-keyring python-requests` .
|
||||
* Run with `./EDMarketConnector.py` .
|
||||
|
||||
Command-line
|
||||
@ -243,6 +251,7 @@ Acknowledgements
|
||||
* Thanks to Taras Velychko for the Ukranian translation.
|
||||
* Thanks to [James Muscat](https://github.com/jamesremuscat) for [EDDN](https://github.com/jamesremuscat/EDDN/wiki) and to [Cmdr Anthor](https://github.com/AnthorNet) for the [stats](http://eddn-gateway.elite-markets.net/).
|
||||
* Thanks to [Andargor](https://github.com/Andargor) for the idea of using the “Companion” interface in [edce-client](https://github.com/Andargor/edce-client).
|
||||
* Uses [Python Keyring Lib](https://github.com/jaraco/keyring) by Jason R. Coombs, Kang Zhang, et al.
|
||||
* Uses [Sparkle](https://github.com/sparkle-project/Sparkle) by [Andy Matuschak](http://andymatuschak.org/) and the [Sparkle Project](https://github.com/sparkle-project).
|
||||
* Uses [WinSparkle](https://github.com/vslavik/winsparkle/wiki) by [Václav Slavík](https://github.com/vslavik).
|
||||
* Uses [OneSky](http://www.oneskyapp.com/) for [translation management](https://marginal.oneskyapp.com/collaboration/project?id=52710).
|
||||
|
44
companion.py
44
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):
|
||||
|
35
config.py
35
config.py
@ -1,3 +1,4 @@
|
||||
import keyring
|
||||
import numbers
|
||||
import sys
|
||||
from os import getenv, makedirs, mkdir, pardir
|
||||
@ -8,7 +9,7 @@ from sys import platform
|
||||
appname = 'EDMarketConnector'
|
||||
applongname = 'E:D Market Connector'
|
||||
appcmdname = 'EDMC'
|
||||
appversion = '2.2.5.0'
|
||||
appversion = '2.2.6.1'
|
||||
|
||||
update_feed = 'https://marginal.org.uk/edmarketconnector.xml'
|
||||
update_interval = 47*60*60
|
||||
@ -130,12 +131,12 @@ class Config:
|
||||
|
||||
if not getattr(sys, 'frozen', False):
|
||||
# Don't use Python's settings if interactive
|
||||
self.bundle = 'uk.org.marginal.%s' % appname.lower()
|
||||
NSBundle.mainBundle().infoDictionary()['CFBundleIdentifier'] = self.bundle
|
||||
self.identifier = 'uk.org.marginal.%s' % appname.lower()
|
||||
NSBundle.mainBundle().infoDictionary()['CFBundleIdentifier'] = self.identifier
|
||||
else:
|
||||
self.bundle = NSBundle.mainBundle().bundleIdentifier()
|
||||
self.identifier = NSBundle.mainBundle().bundleIdentifier()
|
||||
self.defaults = NSUserDefaults.standardUserDefaults()
|
||||
self.settings = dict(self.defaults.persistentDomainForName_(self.bundle) or {}) # make writeable
|
||||
self.settings = dict(self.defaults.persistentDomainForName_(self.identifier) or {}) # make writeable
|
||||
|
||||
# Check out_dir exists
|
||||
if not self.get('outdir') or not isdir(self.get('outdir')):
|
||||
@ -161,7 +162,7 @@ class Config:
|
||||
self.settings.pop(key, None)
|
||||
|
||||
def save(self):
|
||||
self.defaults.setPersistentDomain_forName_(self.settings, self.bundle)
|
||||
self.defaults.setPersistentDomain_forName_(self.settings, self.identifier)
|
||||
self.defaults.synchronize()
|
||||
|
||||
def close(self):
|
||||
@ -188,6 +189,8 @@ class Config:
|
||||
|
||||
self.respath = dirname(getattr(sys, 'frozen', False) and sys.executable or __file__)
|
||||
|
||||
self.identifier = applongname
|
||||
|
||||
self.hkey = HKEY()
|
||||
disposition = DWORD()
|
||||
if RegCreateKeyEx(HKEY_CURRENT_USER, r'Software\Marginal\EDMarketConnector', 0, None, 0, KEY_ALL_ACCESS, None, ctypes.byref(self.hkey), ctypes.byref(disposition)):
|
||||
@ -281,6 +284,8 @@ class Config:
|
||||
|
||||
self.respath = dirname(__file__)
|
||||
|
||||
self.identifier = 'uk.org.marginal.%s' % appname.lower()
|
||||
|
||||
self.filename = join(getenv('XDG_CONFIG_HOME', expanduser('~/.config')), appname, '%s.ini' % appname)
|
||||
if not isdir(dirname(self.filename)):
|
||||
makedirs(dirname(self.filename))
|
||||
@ -298,7 +303,7 @@ class Config:
|
||||
try:
|
||||
val = self.config.get(self.SECTION, key)
|
||||
if u'\n' in val:
|
||||
return val.split(u'\n')
|
||||
return val.split(u'\n')[:-1]
|
||||
else:
|
||||
return val
|
||||
except:
|
||||
@ -314,7 +319,7 @@ class Config:
|
||||
if isinstance(val, basestring) or isinstance(val, numbers.Integral):
|
||||
self.config.set(self.SECTION, key, val)
|
||||
elif hasattr(val, '__iter__'): # iterable
|
||||
self.config.set(self.SECTION, key, u'\n'.join([unicode(x) for x in val]))
|
||||
self.config.set(self.SECTION, key, u'\n'.join([unicode(x) for x in val] + [u';']))
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
||||
@ -334,5 +339,19 @@ class Config:
|
||||
def __init__(self):
|
||||
raise NotImplementedError('Implement me')
|
||||
|
||||
# Common
|
||||
|
||||
def get_password(self, account):
|
||||
return keyring.get_password(self.identifier, account)
|
||||
|
||||
def set_password(self, account, password):
|
||||
keyring.set_password(self.identifier, account, password)
|
||||
|
||||
def delete_password(self, account):
|
||||
try:
|
||||
keyring.delete_password(self.identifier, account)
|
||||
except:
|
||||
pass # don't care - silently fail
|
||||
|
||||
# singleton
|
||||
config = Config()
|
||||
|
6
edsm.py
6
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
|
||||
|
10
monitor.py
10
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
|
||||
@ -163,7 +165,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
|
||||
def on_created(self, event):
|
||||
# watchdog callback, e.g. client (re)started.
|
||||
if not event.is_directory and basename(event.src_path).startswith('Journal.'):
|
||||
if not event.is_directory and basename(event.src_path).startswith('Journal.') and basename(event.src_path).endswith('.log'):
|
||||
self.logfile = event.src_path
|
||||
|
||||
def worker(self):
|
||||
@ -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('<<JournalEvent>>', when="tail")
|
||||
else:
|
||||
loghandle = None
|
||||
|
||||
@ -197,7 +197,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
else:
|
||||
# Poll
|
||||
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')])
|
||||
newlogfile = logfiles and join(self.currentdir, logfiles[-1]) or None
|
||||
except:
|
||||
if __debug__: print_exc()
|
||||
|
159
prefs.py
159
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 = False # Note if Cmdr changes in the Journal
|
||||
|
||||
frame = ttk.Frame(self)
|
||||
frame.grid(sticky=tk.NSEW)
|
||||
|
||||
notebook = nb.Notebook(frame)
|
||||
notebook.bind('<<NotebookTabChanged>>', 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)
|
||||
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)
|
||||
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
|
||||
|
||||
@ -323,30 +338,59 @@ 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 or _('None')) + (monitor.is_beta and ' [Beta]' or '') # No hotkey/shortcut currently defined
|
||||
if self.cmdr != monitor.cmdr:
|
||||
# Cmdr has changed - update settings
|
||||
self.username['state'] = tk.NORMAL
|
||||
self.username.delete(0, tk.END)
|
||||
self.password['state'] = tk.NORMAL
|
||||
self.password.delete(0, tk.END)
|
||||
self.edsm_user['state'] = tk.NORMAL
|
||||
self.edsm_user.delete(0, tk.END)
|
||||
self.edsm_apikey['state'] = tk.NORMAL
|
||||
self.edsm_apikey.delete(0, tk.END)
|
||||
if monitor.cmdr and config.get('cmdrs') and monitor.cmdr in config.get('cmdrs'):
|
||||
config_idx = config.get('cmdrs').index(monitor.cmdr)
|
||||
self.username.insert(0, config.get('fdev_usernames')[config_idx] or '')
|
||||
self.password.insert(0, config.get_password(config.get('fdev_usernames')[config_idx]) or '')
|
||||
self.edsm_user.insert(0, config.get('edsm_usernames')[config_idx] or '')
|
||||
self.edsm_apikey.insert(0, config.get('edsm_apikeys')[config_idx] or '')
|
||||
elif monitor.cmdr and not config.get('cmdrs') and config.get('username') and config.get('password'):
|
||||
# migration from <= 2.25
|
||||
self.username.insert(0, config.get('username') or '')
|
||||
self.password.insert(0, config.get('password') or '')
|
||||
self.edsm_user.insert(0,config.get('edsm_cmdrname') or '')
|
||||
self.edsm_apikey.insert(0, config.get('edsm_apikey') 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':
|
||||
@ -476,9 +520,22 @@ 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 self.password.get().strip():
|
||||
config.set_password(self.username.get().strip(), self.password.get().strip()) # Can fail if keyring not unlocked
|
||||
else:
|
||||
config.delete_password(self.username.get().strip()) # user may have cleared the password field
|
||||
if not config.get('cmdrs'):
|
||||
config.set('cmdrs', [self.cmdr])
|
||||
config.set('fdev_usernames', [self.username.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('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) +
|
||||
@ -491,9 +548,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
|
||||
@ -623,3 +677,34 @@ 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_password(config.get('username'), config.get('password')) # Can fail on Linux
|
||||
config.set('cmdrs', [current_cmdr])
|
||||
config.set('fdev_usernames', [config.get('username')])
|
||||
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('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)
|
||||
|
@ -1,4 +1,5 @@
|
||||
argh>=0.26.2
|
||||
keyring>=3.3
|
||||
pathtools>=0.1.2
|
||||
PyYAML>=3.12
|
||||
requests>=2.11.1
|
||||
|
4
setup.py
4
setup.py
@ -70,7 +70,7 @@ if sys.platform=='darwin':
|
||||
OPTIONS = { 'py2app':
|
||||
{'dist_dir': dist_dir,
|
||||
'optimize': 2,
|
||||
'packages': [ 'requests' ],
|
||||
'packages': [ 'requests', 'keyring.backends' ],
|
||||
'frameworks': [ 'Sparkle.framework' ],
|
||||
'excludes': [ 'PIL', 'simplejson' ],
|
||||
'iconfile': '%s.icns' % APPNAME,
|
||||
@ -100,7 +100,7 @@ elif sys.platform=='win32':
|
||||
OPTIONS = { 'py2exe':
|
||||
{'dist_dir': dist_dir,
|
||||
'optimize': 2,
|
||||
'packages': [ 'requests' ],
|
||||
'packages': [ 'requests', 'keyring.backends' ],
|
||||
'excludes': [ 'PIL', 'simplejson' ],
|
||||
}
|
||||
}
|
||||
|
4
stats.py
4
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()
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user