Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2051] Config Files Audit #2084

Merged
merged 5 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 32 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
# Ignore version file
.gitversion

# Ignore macOS DS_Store files
.DS_Store

# Ignore build artifacts
build
ChangeLog.html
dist.win32/
dist.*

# Ignore generated ChangeLog.html file
ChangeLog.html

# Ignore files
dump
*.bak
*.pyc
Expand All @@ -11,20 +21,37 @@ dump
*.pdb
*.msi
*.wixobj
*.zip

# Ignore Update Things
EDMarketConnector_Installer_*.exe
appcast_win_*.xml
appcast_mac_*.xml
EDMarketConnector.VisualElementsManifest.xml
*.zip
EDMC_Installer_Config.iss
EDMarketConnector.wxs
wix/components.wxs

# Ignore Visual Elements Manifest file for Windows
EDMarketConnector.VisualElementsManifest.xml

# Ignore IDE and editor configuration files
.idea
.vscode

# Ignore virtual environments
.venv/
venv/
venv2

# Ignore workspace file for Visual Studio Code
*.code-workspace

# Ignore coverage reports
htmlcov/
.ignored
.coverage
EDMarketConnector.wxs
wix/components.wxs
pylintrc
pylint.txt

# Ignore Submodule data directory
coriolis-data/
61 changes: 32 additions & 29 deletions config/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
"""
Code dealing with the configuration of the program.
__init__.py - Code dealing with the configuration of the program.

Copyright (c) EDCD, All Rights Reserved
Licensed under the GNU General Public License.
See LICENSE file.

Windows uses the Registry to store values in a flat manner.
Linux uses a file, but for commonality it's still a flat data structure.
macOS uses a 'defaults' object.
"""

from __future__ import annotations

__all__ = [
# defined in the order they appear in the file
Expand Down Expand Up @@ -40,10 +44,8 @@
import traceback
import warnings
from abc import abstractmethod
from typing import Any, Callable, Optional, Type, TypeVar

from typing import Any, Callable, Type, TypeVar
import semantic_version

from constants import GITVERSION_FILE, applongname, appname

# Any of these may be imported by plugins
Expand All @@ -52,9 +54,9 @@
# <https://semver.org/#semantic-versioning-specification-semver>
# Major.Minor.Patch(-prerelease)(+buildmetadata)
# NB: Do *not* import this, use the functions appversion() and appversion_nobuild()
_static_appversion = '5.9.5'
_static_appversion = '5.10.0-alpha0'

_cached_version: Optional[semantic_version.Version] = None
_cached_version: semantic_version.Version | None = None
copyright = '© 2015-2019 Jonathan Harris, 2020-2023 EDCD'

update_feed = 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml'
Expand All @@ -66,7 +68,7 @@
trace_on: list[str] = []

capi_pretend_down: bool = False
capi_debug_access_token: Optional[str] = None
capi_debug_access_token: str | None = None
# This must be done here in order to avoid an import cycle with EDMCLogging.
# Other code should use EDMCLogging.get_main_logger
if os.getenv("EDMC_NO_UI"):
Expand All @@ -79,7 +81,6 @@
_T = TypeVar('_T')


###########################################################################
def git_shorthash_from_head() -> str:
"""
Determine short hash for current git HEAD.
Expand All @@ -91,13 +92,14 @@ def git_shorthash_from_head() -> str:
shorthash: str = None # type: ignore

try:
git_cmd = subprocess.Popen('git rev-parse --short HEAD'.split(),
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT
)
git_cmd = subprocess.Popen(
"git rev-parse --short HEAD".split(),
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
)
out, err = git_cmd.communicate()

except Exception as e:
except subprocess.CalledProcessError as e:
logger.info(f"Couldn't run git command for short hash: {e!r}")

else:
Expand Down Expand Up @@ -131,7 +133,7 @@ def appversion() -> semantic_version.Version:
if getattr(sys, 'frozen', False):
# Running frozen, so we should have a .gitversion file
# Yes, .parent because if frozen we're inside library.zip
with open(pathlib.Path(sys.path[0]).parent / GITVERSION_FILE, 'r', encoding='utf-8') as gitv:
with open(pathlib.Path(sys.path[0]).parent / GITVERSION_FILE, encoding='utf-8') as gitv:
shorthash = gitv.read()

else:
Expand All @@ -157,11 +159,14 @@ def appversion_nobuild() -> semantic_version.Version:
:return: App version without any build meta data.
"""
return appversion().truncate('prerelease')
###########################################################################


class AbstractConfig(abc.ABC):
"""Abstract root class of all platform specific Config implementations."""
"""
Abstract root class of all platform specific Config implementations.

Commented lines are no longer supported or replaced.
"""

OUT_EDDN_SEND_STATION_DATA = 1
# OUT_MKT_BPC = 2 # No longer supported
Expand All @@ -185,7 +190,6 @@ class AbstractConfig(abc.ABC):
respath_path: pathlib.Path
home_path: pathlib.Path
default_journal_dir_path: pathlib.Path

identifier: str

__in_shutdown = False # Is the application currently shutting down ?
Expand Down Expand Up @@ -241,7 +245,7 @@ def set_eddn_url(self, eddn_url: str):
self.__eddn_url = eddn_url

@property
def eddn_url(self) -> Optional[str]:
def eddn_url(self) -> str | None:
"""
Provide the custom EDDN URL.

Expand Down Expand Up @@ -296,14 +300,14 @@ def default_journal_dir(self) -> str:
def _suppress_call(
func: Callable[..., _T], exceptions: Type[BaseException] | list[Type[BaseException]] = Exception,
*args: Any, **kwargs: Any
) -> Optional[_T]:
) -> _T | None:
if exceptions is None:
exceptions = [Exception]

if not isinstance(exceptions, list):
exceptions = [exceptions]

with contextlib.suppress(*exceptions): # type: ignore # it works fine, mypy
with contextlib.suppress(*exceptions):
return func(*args, **kwargs)

return None
Expand All @@ -326,16 +330,16 @@ def get(
if (a_list := self._suppress_call(self.get_list, ValueError, key, default=None)) is not None:
return a_list

elif (a_str := self._suppress_call(self.get_str, ValueError, key, default=None)) is not None:
if (a_str := self._suppress_call(self.get_str, ValueError, key, default=None)) is not None:
return a_str

elif (a_bool := self._suppress_call(self.get_bool, ValueError, key, default=None)) is not None:
if (a_bool := self._suppress_call(self.get_bool, ValueError, key, default=None)) is not None:
return a_bool

elif (an_int := self._suppress_call(self.get_int, ValueError, key, default=None)) is not None:
if (an_int := self._suppress_call(self.get_int, ValueError, key, default=None)) is not None:
return an_int

return default # type: ignore
return default

@abstractmethod
def get_list(self, key: str, *, default: list | None = None) -> list:
Expand Down Expand Up @@ -462,16 +466,15 @@ def get_config(*args, **kwargs) -> AbstractConfig:
from .darwin import MacConfig
return MacConfig(*args, **kwargs)

elif sys.platform == "win32": # pragma: sys-platform-win32
if sys.platform == "win32": # pragma: sys-platform-win32
from .windows import WinConfig
return WinConfig(*args, **kwargs)

elif sys.platform == "linux": # pragma: sys-platform-linux
if sys.platform == "linux": # pragma: sys-platform-linux
from .linux import LinuxConfig
return LinuxConfig(*args, **kwargs)

else: # pragma: sys-platform-not-known
raise ValueError(f'Unknown platform: {sys.platform=}')
raise ValueError(f'Unknown platform: {sys.platform=}')


config = get_config()
34 changes: 20 additions & 14 deletions config/darwin.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
"""Darwin/macOS implementation of AbstractConfig."""
"""
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, Dict, List, Union

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'
Expand Down Expand Up @@ -48,14 +54,14 @@ def __init__(self) -> None:

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._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) -> Union[None, list, str, int]:
def __raw_get(self, key: str) -> None | list | str | int:
"""
Retrieve the raw data for the given key.

Expand All @@ -82,7 +88,7 @@ def get_str(self, key: str, *, default: str = None) -> 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
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}')
Expand All @@ -97,9 +103,9 @@ def get_list(self, key: str, *, default: list = None) -> 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
return default # Yes it could be None, but we're _assuming_ that people gave us a default

elif not isinstance(res, list):
if not isinstance(res, list):
raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}')

return res
Expand All @@ -114,15 +120,15 @@ def get_int(self, key: str, *, default: int = 0) -> int:
if res is None:
return default

elif not isinstance(res, (str, int)):
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 # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default
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:
"""
Expand All @@ -132,14 +138,14 @@ def get_bool(self, key: str, *, default: bool = None) -> 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
return default # Yes it could be None, but we're _assuming_ that people gave us a default

elif not isinstance(res, bool):
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: Union[int, str, List[str], bool]) -> None:
def set(self, key: str, val: int | str | list[str] | bool) -> None:
"""
Set the given key's data to the given value.

Expand Down
9 changes: 7 additions & 2 deletions config/linux.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
"""Linux config implementation."""
"""
linux.py - Linux config implementation.

Copyright (c) EDCD, All Rights Reserved
Licensed under the GNU General Public License.
See LICENSE file.
"""
import os
import pathlib
import sys
from configparser import ConfigParser

from config import AbstractConfig, appname, logger

assert sys.platform == 'linux'
Expand Down
Loading