Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
lutris / usr / lib / python3 / dist-packages / lutris / config.py
Size: Mime:
"""Handle the game, runner and global system configurations."""

# Standard Library
import os
import time

# Lutris Modules
from lutris import settings, sysoptions
from lutris.runners import InvalidRunner, import_runner
from lutris.util.log import logger
from lutris.util.system import path_exists
from lutris.util.yaml import read_yaml_from_file, write_yaml_to_file


def make_game_config_id(game_slug):
    """Return an unique config id to avoid clashes between multiple games"""
    return "{}-{}".format(game_slug, int(time.time()))


class LutrisConfig:

    """Class where all the configuration handling happens.

    Description
    ===========
    Lutris' configuration uses a cascading mechanism where
    each higher, more specific level overrides the lower ones

    The levels are (highest to lowest): `game`, `runner` and `system`.
    Each level has its own set of options (config section), available to and
    overridden by upper levels:
    ```
     level | Config sections
    -------|----------------------
      game | system, runner, game
    runner | system, runner
    system | system
    ```
    Example: if requesting runner options at game level, their returned value
    will be from the game level config if it's set at this level; if not it
    will be the value from runner level if available; and if not, the default
    value set in the runner's module, or None.

    The config levels are stored in separate YAML format text files.

    Usage
    =====
    The config level will be auto set depending on what you pass to __init__:
    - For game level, pass game_config_id and optionally runner_slug (better perfs)
    - For runner level, pass runner_slug
    - For system level, pass nothing
    If need be, you can pass the level manually.

    To read, use the config sections dicts: game_config, runner_config and
    system_config.

    To write, modify the relevant `raw_*_config` section dict, then run
    `save()`.

    """

    def __init__(self, runner_slug=None, game_config_id=None, level=None):
        self.game_config_id = game_config_id
        if runner_slug:
            self.runner_slug = str(runner_slug)
        else:
            self.runner_slug = runner_slug

        # Cascaded config sections (for reading)
        self.game_config = {}
        self.runner_config = {}
        self.system_config = {}

        # Raw (non-cascaded) sections (for writing)
        self.raw_game_config = {}
        self.raw_runner_config = {}
        self.raw_system_config = {}

        self.raw_config = {}

        # Set config level
        self.level = level
        if not level:
            if game_config_id:
                self.level = "game"
            elif runner_slug:
                self.level = "runner"
            else:
                self.level = "system"
        self.initialize_config()

    def __repr__(self):
        return "LutrisConfig(level=%s, game_config_id=%s, runner=%s)" % (
            self.level,
            self.game_config_id,
            self.runner_slug,
        )

    @property
    def system_config_path(self):
        return os.path.join(settings.CONFIG_DIR, "system.yml")

    @property
    def runner_config_path(self):
        if not self.runner_slug:
            return None
        return os.path.join(settings.CONFIG_DIR, "runners/%s.yml" % self.runner_slug)

    @property
    def game_config_path(self):
        if not self.game_config_id:
            return None
        return os.path.join(settings.CONFIG_DIR, "games/%s.yml" % self.game_config_id)

    def initialize_config(self):
        """Init and load config files"""
        self.game_level = {"system": {}, self.runner_slug: {}, "game": {}}
        self.runner_level = {"system": {}, self.runner_slug: {}}
        self.system_level = {"system": {}}
        self.game_level.update(read_yaml_from_file(self.game_config_path))
        self.runner_level.update(read_yaml_from_file(self.runner_config_path))
        self.system_level.update(read_yaml_from_file(self.system_config_path))

        self.update_cascaded_config()
        self.update_raw_config()

    def update_cascaded_config(self):
        if self.system_level.get("system") is None:
            self.system_level["system"] = {}
        self.system_config.clear()
        self.system_config.update(self.get_defaults("system"))
        self.system_config.update(self.system_level.get("system"))

        if self.level in ["runner", "game"] and self.runner_slug:
            if self.runner_level.get(self.runner_slug) is None:
                self.runner_level[self.runner_slug] = {}
            if self.runner_level.get("system") is None:
                self.runner_level["system"] = {}
            self.runner_config.clear()
            self.runner_config.update(self.get_defaults("runner"))
            self.runner_config.update(self.runner_level.get(self.runner_slug))
            self.merge_to_system_config(self.runner_level.get("system"))

        if self.level == "game" and self.runner_slug:
            if self.game_level.get("game") is None:
                self.game_level["game"] = {}
            if self.game_level.get(self.runner_slug) is None:
                self.game_level[self.runner_slug] = {}
            if self.game_level.get("system") is None:
                self.game_level["system"] = {}
            self.game_config.clear()
            self.game_config.update(self.get_defaults("game"))
            self.game_config.update(self.game_level.get("game"))
            self.runner_config.update(self.game_level.get(self.runner_slug))
            self.merge_to_system_config(self.game_level.get("system"))

    def merge_to_system_config(self, config):
        """Merge a configuration to the system configuation"""
        if not config:
            return
        existing_env = None
        if self.system_config.get("env") and "env" in config:
            existing_env = self.system_config["env"]
        self.system_config.update(config)
        if existing_env:
            self.system_config["env"] = existing_env
            self.system_config["env"].update(config["env"])

    def update_raw_config(self):
        # Select the right level of config
        if self.level == "game":
            raw_config = self.game_level
        elif self.level == "runner":
            raw_config = self.runner_level
        else:
            raw_config = self.system_level

        # Load config sections
        self.raw_system_config = raw_config["system"]
        if self.level in ["runner", "game"]:
            self.raw_runner_config = raw_config[self.runner_slug]
        if self.level == "game":
            self.raw_game_config = raw_config["game"]

        self.raw_config = raw_config

    def remove(self):
        """Delete the configuration file from disk."""
        if not self.game_config_path:
            raise RuntimeError("Tried to remove a non-existent config")
        if not path_exists(self.game_config_path):
            logger.debug("No config file at %s", self.game_config_path)
            return
        os.remove(self.game_config_path)
        logger.debug("Removed config %s", self.game_config_path)

    def save(self):
        """Save configuration file according to its type"""
        if self.level == "system":
            config = self.system_level
            config_path = self.system_config_path
        elif self.level == "runner":
            config = self.runner_level
            config_path = self.runner_config_path
        elif self.level == "game":
            config = self.game_level
            config_path = self.game_config_path
        else:
            raise ValueError("Invalid config level '%s'" % self.level)

        logger.debug("Saving %s config to %s", self, config_path)
        write_yaml_to_file(config, config_path)
        self.initialize_config()

    def get_defaults(self, options_type):
        """Return a dict of options' default value."""
        options_dict = self.options_as_dict(options_type)
        defaults = {}
        for option, params in options_dict.items():
            if "default" in params:
                defaults[option] = params["default"]
        return defaults

    def options_as_dict(self, options_type):
        """Convert the option list to a dict with option name as keys"""
        if options_type == "system":
            options = (
                sysoptions.with_runner_overrides(self.runner_slug) if self.runner_slug else sysoptions.system_options
            )
        else:
            if not self.runner_slug:
                return None
            attribute_name = options_type + "_options"

            try:
                runner = import_runner(self.runner_slug)
            except InvalidRunner:
                options = {}
            else:
                if not getattr(runner, attribute_name):
                    runner = runner()

                options = getattr(runner, attribute_name)
        return dict((opt["option"], opt) for opt in options)