mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2025-04-12 15:27:14 +03:00
376 lines
14 KiB
Python
376 lines
14 KiB
Python
import keyring
|
|
import numbers
|
|
import sys
|
|
from os import getenv, makedirs, mkdir, pardir
|
|
from os.path import expanduser, dirname, exists, isdir, join, normpath
|
|
from sys import platform
|
|
|
|
|
|
appname = 'EDMarketConnector'
|
|
applongname = 'E:D Market Connector'
|
|
appcmdname = 'EDMC'
|
|
appversion = '3.1.0.0'
|
|
|
|
update_feed = 'https://marginal.org.uk/edmarketconnector.xml'
|
|
update_interval = 47*60*60
|
|
|
|
|
|
if platform=='darwin':
|
|
from Foundation import NSBundle, NSUserDefaults, NSSearchPathForDirectoriesInDomains, NSApplicationSupportDirectory, NSDocumentDirectory, NSLibraryDirectory, NSUserDomainMask
|
|
|
|
elif platform=='win32':
|
|
import ctypes
|
|
from ctypes.wintypes import *
|
|
import uuid
|
|
|
|
FOLDERID_Documents = uuid.UUID('{FDD39AD0-238F-46AF-ADB4-6C85480369C7}')
|
|
FOLDERID_LocalAppData = uuid.UUID('{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}')
|
|
FOLDERID_Profile = uuid.UUID('{5E6C858F-0E22-4760-9AFE-EA3317B67173}')
|
|
FOLDERID_SavedGames = uuid.UUID('{4C5C32FF-BB9D-43b0-B5B4-2D72E54EAAA4}')
|
|
|
|
SHGetKnownFolderPath = ctypes.windll.shell32.SHGetKnownFolderPath
|
|
SHGetKnownFolderPath.argtypes = [ctypes.c_char_p, DWORD, HANDLE, ctypes.POINTER(ctypes.c_wchar_p)]
|
|
|
|
CoTaskMemFree = ctypes.windll.ole32.CoTaskMemFree
|
|
CoTaskMemFree.argtypes = [ctypes.c_void_p]
|
|
|
|
# _winreg that ships with Python 2 doesn't support unicode, so do this instead
|
|
HKEY_CURRENT_USER = 0x80000001
|
|
KEY_ALL_ACCESS = 0x000F003F
|
|
REG_CREATED_NEW_KEY = 0x00000001
|
|
REG_OPENED_EXISTING_KEY = 0x00000002
|
|
REG_SZ = 1
|
|
REG_DWORD = 4
|
|
REG_MULTI_SZ = 7
|
|
|
|
RegCreateKeyEx = ctypes.windll.advapi32.RegCreateKeyExW
|
|
RegCreateKeyEx.restype = LONG
|
|
RegCreateKeyEx.argtypes = [HKEY, LPCWSTR, DWORD, LPCVOID, DWORD, DWORD, LPCVOID, ctypes.POINTER(HKEY), ctypes.POINTER(DWORD)]
|
|
|
|
RegOpenKeyEx = ctypes.windll.advapi32.RegOpenKeyExW
|
|
RegOpenKeyEx.restype = LONG
|
|
RegOpenKeyEx.argtypes = [HKEY, LPCWSTR, DWORD, DWORD, ctypes.POINTER(HKEY)]
|
|
|
|
RegCloseKey = ctypes.windll.advapi32.RegCloseKey
|
|
RegCloseKey.restype = LONG
|
|
RegCloseKey.argtypes = [HKEY]
|
|
|
|
RegQueryValueEx = ctypes.windll.advapi32.RegQueryValueExW
|
|
RegQueryValueEx.restype = LONG
|
|
RegQueryValueEx.argtypes = [HKEY, LPCWSTR, LPCVOID, ctypes.POINTER(DWORD), LPCVOID, ctypes.POINTER(DWORD)]
|
|
|
|
RegSetValueEx = ctypes.windll.advapi32.RegSetValueExW
|
|
RegSetValueEx.restype = LONG
|
|
RegSetValueEx.argtypes = [HKEY, LPCWSTR, LPCVOID, DWORD, LPCVOID, DWORD]
|
|
|
|
RegCopyTree = ctypes.windll.advapi32.RegCopyTreeW
|
|
RegCopyTree.restype = LONG
|
|
RegCopyTree.argtypes = [HKEY, LPCWSTR, HKEY]
|
|
|
|
RegDeleteKey = ctypes.windll.advapi32.RegDeleteTreeW
|
|
RegDeleteKey.restype = LONG
|
|
RegDeleteKey.argtypes = [HKEY, LPCWSTR]
|
|
|
|
RegDeleteValue = ctypes.windll.advapi32.RegDeleteValueW
|
|
RegDeleteValue.restype = LONG
|
|
RegDeleteValue.argtypes = [HKEY, LPCWSTR]
|
|
|
|
def KnownFolderPath(guid):
|
|
buf = ctypes.c_wchar_p()
|
|
if SHGetKnownFolderPath(ctypes.create_string_buffer(guid.bytes_le), 0, 0, ctypes.byref(buf)):
|
|
return None
|
|
retval = buf.value # copy data
|
|
CoTaskMemFree(buf) # and free original
|
|
return retval
|
|
|
|
|
|
elif platform=='linux2':
|
|
import codecs
|
|
# requires python-iniparse package - ConfigParser that ships with Python < 3.2 doesn't support unicode
|
|
from iniparse import RawConfigParser
|
|
|
|
|
|
class Config:
|
|
|
|
OUT_MKT_EDDN = 1
|
|
# OUT_MKT_BPC = 2 # No longer supported
|
|
OUT_MKT_TD = 4
|
|
OUT_MKT_CSV = 8
|
|
OUT_SHIP = 16
|
|
# OUT_SHIP_EDS = 16 # Replaced by OUT_SHIP
|
|
# OUT_SYS_FILE = 32 # No longer supported
|
|
# OUT_STAT = 64 # No longer available
|
|
# OUT_SHIP_CORIOLIS = 128 # Replaced by OUT_SHIP
|
|
OUT_STATION_ANY = OUT_MKT_EDDN|OUT_MKT_TD|OUT_MKT_CSV
|
|
# OUT_SYS_EDSM = 256 # Now a plugin
|
|
# OUT_SYS_AUTO = 512 # Now always automatic
|
|
OUT_MKT_MANUAL = 1024
|
|
OUT_SYS_EDDN = 2048
|
|
OUT_SYS_DELAY = 4096
|
|
|
|
if platform=='darwin':
|
|
|
|
def __init__(self):
|
|
self.app_dir = join(NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, True)[0], appname)
|
|
if not isdir(self.app_dir):
|
|
mkdir(self.app_dir)
|
|
|
|
self.plugin_dir = join(self.app_dir, 'plugins')
|
|
if not isdir(self.plugin_dir):
|
|
mkdir(self.plugin_dir)
|
|
|
|
self.internal_plugin_dir = getattr(sys, 'frozen', False) and normpath(join(dirname(sys.executable.decode(sys.getfilesystemencoding())), pardir, 'Library', 'plugins')) or join(dirname(__file__), 'plugins')
|
|
|
|
self.default_journal_dir = join(NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, True)[0], 'Frontier Developments', 'Elite Dangerous')
|
|
|
|
self.home = expanduser('~')
|
|
|
|
self.respath = getattr(sys, 'frozen', False) and normpath(join(dirname(sys.executable.decode(sys.getfilesystemencoding())), pardir, 'Resources')) or dirname(__file__)
|
|
|
|
if not getattr(sys, 'frozen', False):
|
|
# Don't use Python's settings if interactive
|
|
self.identifier = 'uk.org.marginal.%s' % appname.lower()
|
|
NSBundle.mainBundle().infoDictionary()['CFBundleIdentifier'] = self.identifier
|
|
else:
|
|
self.identifier = NSBundle.mainBundle().bundleIdentifier()
|
|
self.defaults = NSUserDefaults.standardUserDefaults()
|
|
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')):
|
|
self.set('outdir', NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, True)[0])
|
|
|
|
def get(self, key):
|
|
val = self.settings.get(key)
|
|
if val is None:
|
|
return None
|
|
elif hasattr(val, '__iter__'):
|
|
return list(val) # make writeable
|
|
else:
|
|
return unicode(val)
|
|
|
|
def getint(self, key):
|
|
try:
|
|
return int(self.settings.get(key, 0)) # should already be int, but check by casting
|
|
except:
|
|
return 0
|
|
|
|
def set(self, key, val):
|
|
self.settings[key] = val
|
|
|
|
def delete(self, key):
|
|
self.settings.pop(key, None)
|
|
|
|
def save(self):
|
|
self.defaults.setPersistentDomain_forName_(self.settings, self.identifier)
|
|
self.defaults.synchronize()
|
|
|
|
def close(self):
|
|
self.save()
|
|
self.defaults = None
|
|
|
|
elif platform=='win32':
|
|
|
|
def __init__(self):
|
|
|
|
self.app_dir = join(KnownFolderPath(FOLDERID_LocalAppData), appname)
|
|
if not isdir(self.app_dir):
|
|
mkdir(self.app_dir)
|
|
|
|
self.plugin_dir = join(self.app_dir, 'plugins')
|
|
if not isdir(self.plugin_dir):
|
|
mkdir(self.plugin_dir)
|
|
|
|
self.internal_plugin_dir = join(dirname(getattr(sys, 'frozen', False) and sys.executable.decode(sys.getfilesystemencoding()) or __file__), u'plugins')
|
|
|
|
# expanduser in Python 2 on Windows doesn't handle non-ASCII - http://bugs.python.org/issue13207
|
|
self.home = KnownFolderPath(FOLDERID_Profile) or u'\\'
|
|
|
|
journaldir = KnownFolderPath(FOLDERID_SavedGames)
|
|
self.default_journal_dir = journaldir and join(journaldir, 'Frontier Developments', 'Elite Dangerous') or None
|
|
|
|
self.respath = dirname(getattr(sys, 'frozen', False) and sys.executable.decode(sys.getfilesystemencoding()) 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)):
|
|
raise Exception()
|
|
|
|
if disposition.value == REG_CREATED_NEW_KEY:
|
|
|
|
# Migrate pre-1.3.4 registry location
|
|
oldkey = HKEY()
|
|
if not RegOpenKeyEx(HKEY_CURRENT_USER, r'Software\EDMarketConnector', 0, KEY_ALL_ACCESS, ctypes.byref(oldkey)):
|
|
RegCopyTree(oldkey, None, self.hkey)
|
|
RegCloseKey(oldkey)
|
|
RegDeleteKey(HKEY_CURRENT_USER, r'Software\EDMarketConnector')
|
|
|
|
# set WinSparkle defaults - https://github.com/vslavik/winsparkle/wiki/Registry-Settings
|
|
sparklekey = HKEY()
|
|
if not RegCreateKeyEx(self.hkey, 'WinSparkle', 0, None, 0, KEY_ALL_ACCESS, None, ctypes.byref(sparklekey), ctypes.byref(disposition)):
|
|
if disposition.value == REG_CREATED_NEW_KEY:
|
|
buf = ctypes.create_unicode_buffer('1')
|
|
RegSetValueEx(sparklekey, 'CheckForUpdates', 0, 1, buf, len(buf)*2)
|
|
buf = ctypes.create_unicode_buffer(unicode(update_interval))
|
|
RegSetValueEx(sparklekey, 'UpdateInterval', 0, 1, buf, len(buf)*2)
|
|
RegCloseKey(sparklekey)
|
|
|
|
if not self.get('outdir') or not isdir(self.get('outdir')):
|
|
self.set('outdir', KnownFolderPath(FOLDERID_Documents) or self.home)
|
|
|
|
def get(self, key):
|
|
typ = DWORD()
|
|
size = DWORD()
|
|
if RegQueryValueEx(self.hkey, key, 0, ctypes.byref(typ), None, ctypes.byref(size)) or typ.value not in [REG_SZ, REG_MULTI_SZ]:
|
|
return None
|
|
buf = ctypes.create_unicode_buffer(size.value / 2)
|
|
if RegQueryValueEx(self.hkey, key, 0, ctypes.byref(typ), buf, ctypes.byref(size)):
|
|
return None
|
|
elif typ.value == REG_MULTI_SZ:
|
|
return [x for x in ctypes.wstring_at(buf, len(buf)-2).split(u'\x00')]
|
|
else:
|
|
return unicode(buf.value)
|
|
|
|
def getint(self, key):
|
|
typ = DWORD()
|
|
size = DWORD(4)
|
|
val = DWORD()
|
|
if RegQueryValueEx(self.hkey, key, 0, ctypes.byref(typ), ctypes.byref(val), ctypes.byref(size)) or typ.value != REG_DWORD:
|
|
return 0
|
|
else:
|
|
return val.value
|
|
|
|
def set(self, key, val):
|
|
if isinstance(val, basestring):
|
|
buf = ctypes.create_unicode_buffer(val)
|
|
RegSetValueEx(self.hkey, key, 0, REG_SZ, buf, len(buf)*2)
|
|
elif isinstance(val, numbers.Integral):
|
|
RegSetValueEx(self.hkey, key, 0, REG_DWORD, ctypes.byref(DWORD(val)), 4)
|
|
elif hasattr(val, '__iter__'): # iterable
|
|
stringval = u'\x00'.join([unicode(x) or u' ' for x in val] + [u'']) # null terminated non-empty strings
|
|
buf = ctypes.create_unicode_buffer(stringval)
|
|
RegSetValueEx(self.hkey, key, 0, REG_MULTI_SZ, buf, len(buf)*2)
|
|
else:
|
|
raise NotImplementedError()
|
|
|
|
def delete(self, key):
|
|
RegDeleteValue(self.hkey, key)
|
|
|
|
def save(self):
|
|
pass # Redundant since registry keys are written immediately
|
|
|
|
def close(self):
|
|
RegCloseKey(self.hkey)
|
|
self.hkey = None
|
|
|
|
elif platform=='linux2':
|
|
|
|
SECTION = 'config'
|
|
|
|
def __init__(self):
|
|
|
|
# http://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
|
|
self.app_dir = join(getenv('XDG_DATA_HOME', expanduser('~/.local/share')), appname)
|
|
if not isdir(self.app_dir):
|
|
makedirs(self.app_dir)
|
|
|
|
self.plugin_dir = join(self.app_dir, 'plugins')
|
|
if not isdir(self.plugin_dir):
|
|
mkdir(self.plugin_dir)
|
|
|
|
self.internal_plugin_dir = join(dirname(__file__), 'plugins')
|
|
|
|
self.default_journal_dir = None
|
|
|
|
self.home = expanduser('~')
|
|
|
|
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))
|
|
|
|
self.config = RawConfigParser()
|
|
try:
|
|
self.config.readfp(codecs.open(self.filename, 'r', 'utf-8'))
|
|
except:
|
|
self.config.add_section(self.SECTION)
|
|
|
|
if not self.get('outdir') or not isdir(self.get('outdir')):
|
|
self.set('outdir', expanduser('~'))
|
|
|
|
def get(self, key):
|
|
try:
|
|
val = self.config.get(self.SECTION, key)
|
|
if u'\n' in val:
|
|
return [self._unescape(x) for x in val.split(u'\n')[:-1]]
|
|
else:
|
|
return self._unescape(val)
|
|
except:
|
|
return None
|
|
|
|
def getint(self, key):
|
|
try:
|
|
return self.config.getint(self.SECTION, key)
|
|
except:
|
|
return 0
|
|
|
|
def set(self, key, val):
|
|
if isinstance(val, basestring) or isinstance(val, numbers.Integral):
|
|
self.config.set(self.SECTION, key, self._escape(val))
|
|
elif hasattr(val, '__iter__'): # iterable
|
|
self.config.set(self.SECTION, key, u'\n'.join([self._escape(x) for x in val] + [u';']))
|
|
else:
|
|
raise NotImplementedError()
|
|
|
|
def delete(self, key):
|
|
self.config.remove_option(self.SECTION, key)
|
|
|
|
def save(self):
|
|
with codecs.open(self.filename, 'w', 'utf-8') as h:
|
|
h.write(unicode(self.config.data))
|
|
|
|
def close(self):
|
|
self.save()
|
|
self.config = None
|
|
|
|
def _escape(self, val):
|
|
return unicode(val).replace(u'\\', u'\\\\').replace(u'\n', u'\\n').replace(u';', u'\\;')
|
|
|
|
def _unescape(self, val):
|
|
chars = list(val)
|
|
i = 0
|
|
while i < len(chars):
|
|
if chars[i] == '\\':
|
|
chars.pop(i)
|
|
if chars[i] == 'n':
|
|
chars[i] = '\n'
|
|
i += 1
|
|
return u''.join(chars)
|
|
|
|
else: # ???
|
|
|
|
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()
|