mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2025-04-17 17:42:20 +03:00
Does what it says on the tin. Moves config implementations out to individual files, guards those around platforms to make stuff more reasonably split out.
185 lines
6.4 KiB
Python
185 lines
6.4 KiB
Python
import pathlib
|
|
import sys
|
|
from typing import Any, Dict, List, Union
|
|
|
|
from Foundation import ( # type: ignore
|
|
NSApplicationSupportDirectory, NSBundle, NSDocumentDirectory, NSSearchPathForDirectoriesInDomains, NSUserDefaults,
|
|
NSUserDomainMask
|
|
)
|
|
|
|
from config import AbstractConfig, appname, logger
|
|
|
|
assert sys.platform == 'darwin'
|
|
|
|
|
|
class MacConfig(AbstractConfig):
|
|
"""MacConfig is the implementation of AbstractConfig for Darwin based OSes."""
|
|
|
|
def __init__(self) -> None:
|
|
super().__init__()
|
|
support_path = pathlib.Path(
|
|
NSSearchPathForDirectoriesInDomains(
|
|
NSApplicationSupportDirectory, NSUserDomainMask, True
|
|
)[0]
|
|
)
|
|
|
|
self.app_dir_path = support_path / appname
|
|
self.app_dir_path.mkdir(exist_ok=True)
|
|
|
|
self.plugin_dir_path = self.app_dir_path / 'plugins'
|
|
self.plugin_dir_path.mkdir(exist_ok=True)
|
|
|
|
# Bundle IDs identify a singled app though out a system
|
|
|
|
if getattr(sys, 'frozen', False):
|
|
exe_dir = pathlib.Path(sys.executable).parent
|
|
self.internal_plugin_dir_path = exe_dir.parent / 'Library' / 'plugins'
|
|
self.respath_path = exe_dir.parent / 'Resources'
|
|
self.identifier = NSBundle.mainBundle().bundleIdentifier()
|
|
|
|
else:
|
|
file_dir = pathlib.Path(__file__).parent.parent
|
|
self.internal_plugin_dir_path = file_dir / 'plugins'
|
|
self.respath_path = file_dir
|
|
|
|
self.identifier = f'uk.org.marginal.{appname.lower()}'
|
|
NSBundle.mainBundle().infoDictionary()['CFBundleIdentifier'] = self.identifier
|
|
|
|
self.default_journal_dir_path = support_path / 'Frontier Developments' / 'Elite Dangerous'
|
|
self._defaults: Any = NSUserDefaults.standardUserDefaults()
|
|
self._settings: Dict[str, Union[int, str, list]] = dict(
|
|
self._defaults.persistentDomainForName_(self.identifier) or {}
|
|
) # make writeable
|
|
|
|
if (out_dir := self.get_str('out_dir')) is None or not pathlib.Path(out_dir).exists():
|
|
self.set('outdir', NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, True)[0])
|
|
|
|
def __raw_get(self, key: str) -> Union[None, list, str, int]:
|
|
"""
|
|
Retrieve the raw data for the given key.
|
|
|
|
:param str: str - The key data is being requested for.
|
|
:return: The requested data.
|
|
"""
|
|
res = self._settings.get(key)
|
|
# On MacOS Catalina, with python.org python 3.9.2 any 'list'
|
|
# has type __NSCFArray so a simple `isinstance(res, list)` is
|
|
# False. So, check it's not-None, and not the other types.
|
|
#
|
|
# If we can find where to import the definition of NSCFArray
|
|
# then we could possibly test against that.
|
|
if res is not None and not isinstance(res, str) and not isinstance(res, int):
|
|
return list(res)
|
|
|
|
return res
|
|
|
|
def get_str(self, key: str, *, default: str = None) -> str:
|
|
"""
|
|
Return the string referred to by the given key if it exists, or the default.
|
|
|
|
Implements :meth:`AbstractConfig.get_str`.
|
|
"""
|
|
res = self.__raw_get(key)
|
|
if res is None:
|
|
return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default
|
|
|
|
if not isinstance(res, str):
|
|
raise ValueError(f'unexpected data returned from __raw_get: {type(res)=} {res}')
|
|
|
|
return res
|
|
|
|
def get_list(self, key: str, *, default: list = None) -> list:
|
|
"""
|
|
Return the list referred to by the given key if it exists, or the default.
|
|
|
|
Implements :meth:`AbstractConfig.get_list`.
|
|
"""
|
|
res = self.__raw_get(key)
|
|
if res is None:
|
|
return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default
|
|
|
|
elif not isinstance(res, list):
|
|
raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}')
|
|
|
|
return res
|
|
|
|
def get_int(self, key: str, *, default: int = 0) -> int:
|
|
"""
|
|
Return the int referred to by key if it exists in the config.
|
|
|
|
Implements :meth:`AbstractConfig.get_int`.
|
|
"""
|
|
res = self.__raw_get(key)
|
|
if res is None:
|
|
return default
|
|
|
|
elif not isinstance(res, (str, int)):
|
|
raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}')
|
|
|
|
try:
|
|
return int(res)
|
|
|
|
except ValueError as e:
|
|
logger.error(f'__raw_get returned {res!r} which cannot be parsed to an int: {e}')
|
|
return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default
|
|
|
|
def get_bool(self, key: str, *, default: bool = None) -> bool:
|
|
"""
|
|
Return the bool referred to by the given key if it exists, or the default.
|
|
|
|
Implements :meth:`AbstractConfig.get_bool`.
|
|
"""
|
|
res = self.__raw_get(key)
|
|
if res is None:
|
|
return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default
|
|
|
|
elif not isinstance(res, bool):
|
|
raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}')
|
|
|
|
return res
|
|
|
|
def set(self, key: str, val: Union[int, str, List[str], bool]) -> None:
|
|
"""
|
|
Set the given key's data to the given value.
|
|
|
|
Implements :meth:`AbstractConfig.set`.
|
|
"""
|
|
if self._settings is None:
|
|
raise ValueError('attempt to use a closed _settings')
|
|
|
|
if not isinstance(val, (bool, str, int, list)):
|
|
raise ValueError(f'Unexpected type for value {type(val)=}')
|
|
|
|
self._settings[key] = val
|
|
|
|
def delete(self, key: str, *, suppress=False) -> None:
|
|
"""
|
|
Delete the given key from the config.
|
|
|
|
Implements :meth:`AbstractConfig.delete`.
|
|
"""
|
|
try:
|
|
del self._settings[key]
|
|
|
|
except Exception:
|
|
if suppress:
|
|
pass
|
|
|
|
def save(self) -> None:
|
|
"""
|
|
Save the current configuration.
|
|
|
|
Implements :meth:`AbstractConfig.save`.
|
|
"""
|
|
self._defaults.setPersistentDomain_forName_(self._settings, self.identifier)
|
|
self._defaults.synchronize()
|
|
|
|
def close(self) -> None:
|
|
"""
|
|
Close this config and release any associated resources.
|
|
|
|
Implements :meth:`AbstractConfig.close`.
|
|
"""
|
|
self.save()
|
|
self._defaults = None
|