mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2025-04-13 07:47:14 +03:00
192 lines
6.5 KiB
Python
192 lines
6.5 KiB
Python
"""
|
|
darwin.py - Darwin/macOS implementation of AbstractConfig.
|
|
|
|
Copyright (c) EDCD, All Rights Reserved
|
|
Licensed under the GNU General Public License.
|
|
See LICENSE file.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import pathlib
|
|
import sys
|
|
from typing import Any
|
|
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, 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) -> 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 # 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 # Yes it could be None, but we're _assuming_ that people gave us a default
|
|
|
|
if 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
|
|
|
|
if 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 # 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 # Yes it could be None, but we're _assuming_ that people gave us a default
|
|
|
|
if not isinstance(res, bool):
|
|
raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}')
|
|
|
|
return res
|
|
|
|
def set(self, key: str, val: 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
|