1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-04-12 23:37:14 +03:00
EDMarketConnector/config.py
2020-06-21 16:31:41 +01:00

332 lines
12 KiB
Python

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.4.4.0'
update_feed = 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/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
from winreg import CloseKey, CreateKeyEx, OpenKeyEx, DeleteValue, QueryValueEx, SetValueEx, HKEY_CURRENT_USER, KEY_ALL_ACCESS, REG_SZ, REG_DWORD, REG_MULTI_SZ
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]
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=='linux':
import codecs
from configparser import RawConfigParser
class Config(object):
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), 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), 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 isinstance(val, str):
return str(val)
elif hasattr(val, '__iter__'):
return list(val) # make writeable
else:
return None
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 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 or __file__)
self.identifier = applongname
self.hkey = CreateKeyEx(HKEY_CURRENT_USER, r'Software\Marginal\EDMarketConnector', access=KEY_ALL_ACCESS)
try:
sparklekey = OpenKeyEx(hkey, 'WinSparkle')
except:
# set WinSparkle defaults - https://github.com/vslavik/winsparkle/wiki/Registry-Settings
sparklekey = CreateKeyEx(self.hkey, 'WinSparkle', access=KEY_ALL_ACCESS)
SetValueEx(sparklekey, 'CheckForUpdates', 0, REG_SZ, '1')
SetValueEx(sparklekey, 'UpdateInterval', 0, REG_SZ, str(update_interval))
CloseKey(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):
try:
(value, typ) = QueryValueEx(self.hkey, key)
except:
return None
if typ not in [REG_SZ, REG_MULTI_SZ]:
return None
return value
def getint(self, key):
try:
(value, typ) = QueryValueEx(self.hkey, key)
except:
return None
if typ != REG_DWORD:
return None
return value
def set(self, key, val):
if isinstance(val, str):
SetValueEx(self.hkey, key, 0, REG_SZ, val)
elif isinstance(val, numbers.Integral):
SetValueEx(self.hkey, key, 0, REG_DWORD, val)
elif hasattr(val, '__iter__'): # iterable
SetValueEx(self.hkey, key, 0, REG_MULTI_SZ, val)
else:
raise NotImplementedError()
def delete(self, key):
try:
DeleteValue(self.hkey, key)
except:
pass
def save(self):
pass # Redundant since registry keys are written immediately
def close(self):
CloseKey(self.hkey)
self.hkey = None
elif platform=='linux':
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(comment_prefixes = ('#',))
try:
with codecs.open(self.filename, 'r') as h:
self.config.read_file(h)
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: # list
# ConfigParser drops the last entry if blank, so we add a spurious ';' entry in set() and remove it here
assert val.split('\n')[-1] == ';', val.split('\n')
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, bool):
self.config.set(self.SECTION, key, val and '1' or '0')
elif isinstance(val, str) 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:
self.config.write(h)
def close(self):
self.save()
self.config = None
def _escape(self, val):
return str(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):
try:
import keyring
return keyring.get_password(self.identifier, account)
except ImportError:
return None
def set_password(self, account, password):
try:
import keyring
keyring.set_password(self.identifier, account, password)
except ImportError:
pass
def delete_password(self, account):
try:
import keyring
keyring.delete_password(self.identifier, account)
except:
pass # don't care - silently fail
# singleton
config = Config()