Skip to content

Commit

Permalink
Add settings & Refactor code
Browse files Browse the repository at this point in the history
  • Loading branch information
selfkilla666 committed Dec 21, 2023
1 parent eef4cc3 commit 7125ec5
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 56 deletions.
27 changes: 6 additions & 21 deletions crasher/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,10 @@

from __future__ import annotations

from crasher.widgets.application import QCrasherApplication

import sys
import argparse
import platform

from loguru import logger

from crasher.widgets.application import QCrasherApplication
from crasher.utils.path import get_user_local_directory


def get_run_arguments() -> argparse.Namespace:
Expand All @@ -32,22 +28,11 @@ def run_application() -> None:

args = get_run_arguments()

# Setup logger
logger.add(
str(get_user_local_directory()) + r"\logs\log_{time}.log",
format="{time:HH:mm:ss.SS} ({file}) [{level}] {message}",
colorize=True
)

logger.info("Application starts")

# Log debug data about user computer
logger.debug(f"Platform: {platform.system()} {platform.release()} ({platform.architecture()[0]})")
if sys.argv[1:]:
logger.debug(f"Running application with arguments: {sys.argv[1:]}")

# Setup application
application: QCrasherApplication = QCrasherApplication(sys.argv, arguments_=args, logger_=logger)
application: QCrasherApplication = QCrasherApplication(sys.argv, arguments_=args)

# Set the global exception handler
sys.excepthook = application.handle_exception

# Execute application
application.exec()
Expand Down
89 changes: 89 additions & 0 deletions crasher/classes/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Code by @selfkilla666
# https://github.com/witch-software/crasher
# MIT License

from __future__ import annotations

from typing import Any, Optional
from pathlib import Path

from crasher.utils.validate import are_keys_present, add_missing_values

import os.path
import toml
import loguru


DEFAULT_SETTINGS: dict[str, Any] = {
"General": {
"Interface": {
"Theme": "Light"
},
"Language": "en_US"
}
}


class QCrasherApplicationSettings:

path: Path
data: dict[Any, Any]
logger: Optional[loguru.Logger]

def __init__(self, path: Path, *, logger: Optional[loguru.Logger] = None) -> None:
self.path = path
self.logger = logger

def load_settings(self, default_settings=DEFAULT_SETTINGS) -> None:

if self.logger:
self.logger.info(f"Try to load settings from \"{self.path}\"...")

loaded_toml_data = default_settings

if os.path.exists(self.path):
try:
loaded_toml_data = toml.load(self.path)

if not are_keys_present(loaded_toml_data, default_settings):

if self.logger:
self.logger.warning(
"Settings do not contain the required values. They will be replaced by default values")

loaded_toml_data = add_missing_values(loaded_toml_data, default_settings)

except Exception as e:
if self.logger:
self.logger.error(f"Failed to load settings from \"{self.path}\": {e}")

self.data = loaded_toml_data

def save_settings(self) -> None:
with open(self.path, "w") as toml_file:
toml.dump(self.data, toml_file)

if self.logger:
self.logger.success(f"Settings were successfully saved to a file \"{self.path}\"!")

def get_value(self, key: Any, default: Any = None) -> Any:
keys = key.split('.')
current_dict = self.data

# Traverse the dictionary to get the value
for k in keys:
current_dict = current_dict.get(k, {})
if not current_dict:
return default

return current_dict or default

def set_value(self, key: Any, value: Any) -> None:
keys = key.split('.')
current_dict = self.data

# Traverse the dictionary to set the value and create missing sections
for k in keys[:-1]:
current_dict = current_dict.setdefault(k, {})

current_dict[keys[-1]] = value
1 change: 1 addition & 0 deletions crasher/utils/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def load_json_file(path: Path) -> dict[Any, Any]:
with open(path, 'r') as file:
return load(file)


def save_json_file(dictionary: dict[Any, Any], path: Path) -> None:
"""
Save dictionary to the JSON file.
Expand Down
9 changes: 8 additions & 1 deletion crasher/utils/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,18 @@ def are_keys_present(dictionary_to_check: dict[Any, Any], dictionary_pattern: di
"""

for key, value in dictionary_pattern.items():
if key not in dictionary_to_check or not are_keys_present(dictionary_to_check[key], value):
if key not in dictionary_to_check:
return False

if isinstance(value, dict):
if not isinstance(dictionary_to_check[key], dict) or not are_keys_present(dictionary_to_check[key], value):
return False
elif value is not None and dictionary_to_check[key] != value:
return False

return True


def add_missing_values(dictionary_to_check: dict[Any, Any], dictionary_pattern: dict[Any, Any]) -> dict[Any, Any]:
"""
Add missing keys and their corresponding values from `dictionary_pattern` to
Expand Down
102 changes: 74 additions & 28 deletions crasher/widgets/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@

from pathlib import Path

from PySide6.QtWidgets import QApplication, QMainWindow
from PySide6.QtWidgets import QApplication
from PySide6.QtGui import QIcon

from crasher.widgets.window import QCrasherWindow
from crasher.classes.settings import QCrasherApplicationSettings
from crasher.utils.path import get_user_local_directory

import loguru
import platform


__all__ = ["QCrasherApplication"]
Expand All @@ -22,52 +25,65 @@
class QCrasherApplication(QApplication):
""" Custom PySide6.QApplication implementation for Crasher """

APPLICATION_TITLE: str = "Crasher"
APPLICATION_NAME: str = "Crasher"
APPLICATION_VERSION: str = "1.0.0b"
APPLICATION_ORG_NAME: str = "Witch Software"
APPLICATION_ORG_DOMAIN: str = "witch-software.com"

run_arguments: Namespace
logger: loguru.Logger
window: QMainWindow
settings: QCrasherApplicationSettings
window: QCrasherWindow

def __init__(self, argv: list[str], *, arguments_: Namespace, logger_: loguru.Logger) -> None:
debug: bool = True

def __init__(self, argv: list[str], *, arguments_: Namespace) -> None:
"""
Initialize the QCrasherApplication.
:type argv: list[str]
:type arguments_: argparse.Namespace
:type logger_: loguru.Logger
:param argv: Command-line arguments passed to the application.
:param arguments_: Parsed command-line arguments for configuration.
:param logger_: An instance of the logger for logging application events.
:rtype: None
"""

# Todo: Add settings config
self.initialize_logger()
self.logger.info("Application starts")

super(QCrasherApplication, self).__init__(argv)

self.run_arguments = arguments_
self.logger = logger_
self.debug = self.run_arguments.debug or True

self.initialize_application()
self.log_debug_info(argv)

self.logger.info("Start application initialization...")

# Initialize application parts
self.initialize_application_information()
self.initialize_settings()
self.initialize_window()

def initialize_application(self) -> None:
"""
Method to initialize the application.
self.logger.success("Application is fully initialized!")

:rtype: None
"""
def initialize_logger(self) -> None:
""" Method to initialize application logger """

self.logger: loguru.Logger = loguru.logger

# Setup logger
self.logger.add(
str(get_user_local_directory()) + r"\logs\log_{time}.log",
format="{time:HH:mm:ss.SS} ({file}) [{level}] {message}",
colorize=True
)

def initialize_application_information(self) -> None:
""" Method to initialize application information """

# Connect application events
self.aboutToQuit.connect(self.on_close_event)

# Set application metadata
self.setApplicationName(self.APPLICATION_TITLE)
self.setApplicationName(self.APPLICATION_NAME)
self.setApplicationVersion(self.APPLICATION_VERSION)
self.setOrganizationName(self.APPLICATION_ORG_NAME)
self.setOrganizationDomain(self.APPLICATION_ORG_DOMAIN)
Expand All @@ -76,24 +92,54 @@ def initialize_application(self) -> None:
icon = QIcon(str(Path("./static/gui/icons/icon.png")))
self.setWindowIcon(icon)

def initialize_settings(self) -> None:
""" Method to initialize application settings """

self.logger.info("Initialize application settings...")

self.settings: QCrasherApplicationSettings = QCrasherApplicationSettings(
Path(f"{get_user_local_directory()}\\settings.toml"),
logger=self.logger
)
self.settings.load_settings()

def initialize_window(self) -> None:
""" Method to initialize application window """

self.logger.info("Initialize application window...")

if not self.run_arguments.windowless:

self.window: QCrasherWindow = QCrasherWindow(application=self)
self.window.show()

self.logger.success("Window initialized!")

else:
self.logger.info("Application is running in windowless mode")

def set_window(self, window: QMainWindow) -> None:
self.window = window
def log_debug_info(self, argv: list[str]):
""" Logging some values that might help for debugging """

if self.debug:
self.logger.debug("Application running in debug mode.")

self.logger.debug(f"Application version: {self.APPLICATION_VERSION}")

# Debug data about user system
self.logger.debug(f"Platform: {platform.system()} {platform.release()} ({platform.architecture()[0]})")

if len(argv) > 1:
self.logger.debug(f"Running application with arguments: {' '.join(argv[1:])}")

def handle_exception(self, exc_type, exc_value, exc_traceback) -> None:
self.logger.exception("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback))

def on_close_event(self) -> None:
"""
Event called when closing the application.
""" Event called when closing the application """

:rtype: None
"""
self.logger.info("Saving application settings...")

# Todo: Add saving settings config
self.settings.save_settings()

self.logger.success("Application was successfully closed.")
self.logger.success("Application was successfully closed!")
8 changes: 2 additions & 6 deletions crasher/widgets/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,10 @@ def __init__(self, *, application: QApplication) -> None:

self.application: QApplication = application
self.logger: loguru.Logger = self.application.logger # type: ignore[attr-defined]

self.logger.info("Initialize application window...")

self.initialize_ui()

self.logger.success("Window initialized!")
self.initialize_ui() # TODO: Fix this typehint sometime in future ¯\_(ツ)_/¯

def initialize_ui(self) -> None:

self.logger.info("Initializing UI...")

# Set window info
Expand Down

0 comments on commit 7125ec5

Please sign in to comment.