Module geode.lib.game

Expand source code
# ------------------------------------------------------------------------------
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 3 of the License.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#  MA 02110-1301, USA.
# ------------------------------------------------------------------------------

# API
from geode.db import Database

from geode.utils import generate_identifier

from geode.lib.emulator import Emulator

# Datetime
from datetime import date
from datetime import datetime
from datetime import timedelta

# Filesystem
from os.path import dirname
from os.path import basename
from os.path import expanduser

from pathlib import Path

# Regex
from re import compile as re_compile

# System
from os import environ

from copy import deepcopy

from shlex import split as shlex_split

from subprocess import Popen

# ------------------------------------------------------------------------------
#   Modules
# ------------------------------------------------------------------------------

class Game(object):

    attributes = (
        ("filename", None),
        ("name", None),
        ("favorite", False),
        ("multiplayer", False),
        ("finish", False),
        ("score", int()),
        ("played", int()),
        ("play_time", timedelta()),
        ("last_launch_date", date(1, 1, 1)),
        ("last_launch_time", timedelta()),
        ("emulator", None),
        ("arguments", None),
        ("gameid", None),
        ("tags", list()),
        ("cover", None))


    def __init__(self, api, console, filepath):
        """ Constructor

        Parameters
        ----------
        api : geode.api.API
            API instance
        console : geode.lib.console.Console
            Parent console instance
        filepath : pathlib.Path
            Object configuration file
        """

        # ----------------------------------------
        #   Check api parameters
        # ----------------------------------------

        if type(api) is None:
            raise TypeError("Wrong type for api, expected geode.api.API")

        self.api = api

        # ----------------------------------------
        #   Variables
        # ----------------------------------------

        # Store console instance
        self.console = console

        # Store an uniq identifier
        self.id = generate_identifier(filepath)

        # Store full game path
        self.path = filepath

        # Store game environment keys
        self.environment = dict()

        # Store game installation date
        self.installed = datetime.fromtimestamp(filepath.stat().st_ctime).date()

        # Store fullscreen status
        self.fullscreen = False

        # ----------------------------------------
        #   Initialization
        # ----------------------------------------

        # Initialize metadata
        self.__init_game()


    def __init_game(self):
        """ Initialize game metadata from database content
        """

        # Retrieve game metadata from database
        metadata = self.api.get_database().select("games", "*",
            where=[("filename", Database.Condition.EQUAL, self.path.name)])

        # This games owned some data in database
        if len(metadata) > 0:
            metadata = metadata[0]

        # ----------------------------------------
        #   Initialization
        # ----------------------------------------

        # Retrieve time from a string (HH:MM:SS.MS)
        regex = re_compile(r"(\d+):(\d+):(\d+)[\.\d*]?")

        for key, value in self.attributes:
            index = self.attributes.index((key, value))

            if len(metadata) > 0 and len(metadata) >= index:

                # ----------------------------------------
                #   Manage specific variable
                # ----------------------------------------

                if type(value) is bool:
                    value = bool(metadata[index])

                elif type(value) is int:
                    value = int(metadata[index])

                elif type(value) is list:

                    if len(metadata[index]) > 0:
                        value = metadata[index].split(';')

                elif type(value) is date:

                    # ISO 8601 format (YYYY-MM-DD)
                    if len(metadata[index]) == 10:
                        year, month, day = metadata[index].split('-')

                        value = date(int(year), int(month), int(day))

                    # Remove date with an unknown format
                    else:
                        value = None

                elif type(value) is timedelta:
                    result = regex.match(metadata[index])

                    if result is not None:
                        hours, minutes, seconds = result.groups()

                        value = timedelta(
                            hours=int(hours),
                            minutes=int(minutes),
                            seconds=int(seconds))

                    else:
                        value = None

                elif len(metadata[index]) > 0:
                    value = metadata[index]

                # ----------------------------------------
                #   Manage specific constant
                # ----------------------------------------

                if type(value) is str and len(value) > 0:

                    if key == "cover":
                        value = Path(expanduser(value))

                    elif key == "emulator":
                        value = self.api.get_emulator(value)

            # Avoid to have a silly date
            elif type(value) is date:
                value = None

            setattr(self, key, value)

        # Check if a name is unavailable
        if self.name is None or len(self.name) == 0:
            self.name = self.path.stem

        # ----------------------------------------
        #   Environment keys
        # ----------------------------------------

        config = self.api.get_environment()

        if config.has_section(self.id):

            for key, value in config.items(self.id):
                self.environment[key] = value


    def __str__(self):
        """ Print object as string

        Returns
        -------
        str
            Formatted object data as string
        """

        return self.id


    def __get_contents(self, choice):
        """ Retrieve a game folder contents based on an emulator pattern

        Parameters
        ----------
        choice : str
            Emulator pattern attribute (savestate or snapshot)

        Returns
        -------
        list
            Folder contents
        """

        emulator = self.console.emulator

        if self.emulator is not None:
            emulator = self.emulator

        path = getattr(emulator, choice, None)

        if path is not None:
            keys = {
                "rom-name": self.path.stem,
                "rom-name-lower": self.path.stem.lower(),
                "rom-name-upper": self.path.stem.upper(),
                "rom-file": self.path,
                "rom-folder": self.path.parent,
                "gameid": self.gameid }

            pattern = str(path)

            # Avoid to manage empty path
            if len(pattern) > 0:

                for key, value in keys.items():
                    substring = "<%s>" % str(key)

                    # Avoid to continue if wanted key as no value
                    if substring in pattern and value is None:
                        return list()

                    pattern = pattern.replace(substring, str(value))

                # Set a pathlib.Path to allow glob filter
                folder = Path(dirname(pattern))

                # A glob pattern use brackets to match string, so we replace the
                # brackets from a filename with a joker to be sure we retrieve
                # all the corresponding files
                pattern = basename(pattern).replace('[', '?').replace(']', '?')

                return list(sorted(folder.glob(pattern)))

        return list()


    @property
    def snapshots(self):
        """ Retrieve snapshots files list based on emulator pattern

        Returns
        -------
        list
            Snapshots files list
        """

        return self.__get_contents("snapshot")


    @property
    def savestates(self):
        """ Retrieve savestates files list based on emulator pattern

        Returns
        -------
        list
            Savestate files list
        """

        return self.__get_contents("savestate")


    @property
    def command(self):
        """ Generate launch command from game metadata

        Returns
        -------
        list
            Generated command as arguments list
        """

        emulator = self.console.emulator

        if self.emulator is not None:
            emulator = self.emulator

        if not emulator.exists:
            raise OSError(2, "Cannot found binary: %s" % emulator.binary)

        # ----------------------------------------
        #   Retrieve default parameters
        # ----------------------------------------

        arguments = shlex_split(emulator.binary)

        # Retrieve default or specific arguments
        if self.arguments is not None:
            arguments.append(self.arguments)
        elif emulator.default is not None:
            arguments.append(emulator.default)

        # Retrieve fullscreen mode
        if self.fullscreen and emulator.fullscreen is not None:
            arguments.append(emulator.fullscreen)
        elif not self.fullscreen and emulator.windowed is not None:
            arguments.append(emulator.windowed)

        # ----------------------------------------
        #   Replace pattern substitutes
        # ----------------------------------------

        command = ' '.join(arguments).strip()

        need_gamefile = True

        keys = {
            "configuration": emulator.configuration,
            "rom-name": self.path.stem,
            "rom-name-lower": self.path.stem.lower(),
            "rom-name-upper": self.path.stem.upper(),
            "rom-file": self.path,
            "rom-folder": self.path.parent,
            "gameid": self.gameid }

        for key, value in keys.items():
            substring = "<%s>" % key

            if value is not None and substring in command:
                command = command.replace(substring, str(value))

                if key in ("rom-name", "rom-file", "rom-folder"):
                    need_gamefile = False

        # ----------------------------------------
        #   Generate subprocess compatible command
        # ----------------------------------------

        arguments = shlex_split(command)

        if need_gamefile:
            arguments.append(str(self.path))

        return arguments


    def run(self):
        """ Launch a game

        This function avoit to save metadata into database to allow
        multi-threading
        """

        log = self.api.get_storage_path().joinpath("logs", "%s.log" % self.id)

        # Generate logs folder if missing
        if not log.parent.exists():
            log.parent.mkdir(0o755, True)

        # ----------------------------------------
        #   Check environment
        # ----------------------------------------

        emulator = self.console.emulator

        if self.emulator is not None:
            emulator = self.emulator

        # Get a copy of current environment
        environment = environ.copy()

        # Check if game emulator has specific environment variable
        for key, value in emulator.environment.items():
            environment[key] = value

        # Check if game console has specific environment variable
        for key, value in self.console.environment.items():
            environment[key] = value

        # Check if current game has specific environment variable
        for key, value in self.environment.items():
            environment[key] = value

        # ----------------------------------------
        #   Launch game
        # ----------------------------------------

        # Start game timer
        started = datetime.now()

        # Open log file to write stdout
        with open(log, 'w') as pipe:

            # Start game process
            with Popen(self.command, stdout=pipe, stderr=pipe,
                env=environment, universal_newlines=True) as self.process:

                self.api.log.info(
                    "Launch %s with PID %d" % (self.name, self.process.pid))
                self.api.log.debug(
                    "Command: %s" % ' '.join(self.process.args))

                self.api.log.info("Log to %s" % str(log))

                # Write into log file until game stop
                while self.process.stdout is not None:
                    pipe.write(self.process.stdout.read(1))

        # Stop timer and retrieve delta
        delta = datetime.now() - started

        self.api.log.info("Close %s" % self.name)

        # ----------------------------------------
        #   Update metadata
        # ----------------------------------------

        self.played += 1
        self.play_time += delta
        self.last_launch_date = date.today()
        self.last_launch_time = delta


    def copy(self, filepath):
        """ Copy game instance

        Parameters
        ----------
        filepath : pathlib.Path
            Object configuration file

        Returns
        -------
        geode.lib.game.Game
            New game instance
        """

        game = deepcopy(self)

        # Generate a new identifier
        game.id = generate_identifier(filepath)

        # Retrieve name from filename
        game.name = filepath.stem

        # Store filepath
        game.path = filepath

        return game


    def save(self):
        """ Save game instance to database
        """

        database = self.api.get_database()

        # Retrieve game metadata from database
        metadata = database.select("games", "filename",
            where=[("filename", Database.Condition.EQUAL, self.path.name)])

        # Generate a dictionary with database scheme format
        data = dict()

        for key, value in self.attributes:
            value = getattr(self, key, value)

            # ----------------------------------------
            #   Manage specific variable
            # ----------------------------------------

            if value is None:
                value = str()

            elif type(value) is bool:
                value = int(value)

            elif type(value) is list:
                value = ';'.join(value)

            elif type(value) is Path:
                value = value.name

            elif type(value) is Emulator:
                value = value.id

            # ----------------------------------------
            #   Manage specific constant
            # ----------------------------------------

            if key == "filename":
                value = self.path.name

            data[key] = str(value)

        # Update entry from database
        if len(metadata) > 0:
            database.update("games", data=data,
                where=[("filename", Database.Condition.EQUAL, self.path.name)])

        # Generate a new entry into database
        else:
            database.insert("games", data=data)

        # ----------------------------------------
        #   Environment keys
        # ----------------------------------------

        config = self.api.get_environment()

        if len(self.environment) > 0:

            if not config.has_section(self.id):
                config.add_section(self.id)

            for key, value in self.environment.items():
                config.modify(self.id, key, value)

        elif config.has_section(self.id):
            config.remove_section(self.id)

        config.update()


    def delete(self):
        """ Delete game instance from database
        """

        database = self.api.get_database()

        database.delete("games",
            where=[("filename", Database.Condition.EQUAL, self.path.name)])


    def clear_metadata(self):
        """ Clear metadata
        """

        for key, value in self.attributes:
            setattr(self, key, value)

        # Generate a new identifier
        self.id = generate_identifier(self.path)

        # Retrieve name from filename
        self.name = self.path.stem

        # Avoid to have a silly date
        self.last_launch_date = None


    def reset_metadata(self):
        """ Reset game metadata from database
        """

        self.__init_game()

Classes

class Game (api, console, filepath)

Constructor

Parameters

api : API
API instance
console : Console
Parent console instance
filepath : pathlib.Path
Object configuration file
Expand source code
class Game(object):

    attributes = (
        ("filename", None),
        ("name", None),
        ("favorite", False),
        ("multiplayer", False),
        ("finish", False),
        ("score", int()),
        ("played", int()),
        ("play_time", timedelta()),
        ("last_launch_date", date(1, 1, 1)),
        ("last_launch_time", timedelta()),
        ("emulator", None),
        ("arguments", None),
        ("gameid", None),
        ("tags", list()),
        ("cover", None))


    def __init__(self, api, console, filepath):
        """ Constructor

        Parameters
        ----------
        api : geode.api.API
            API instance
        console : geode.lib.console.Console
            Parent console instance
        filepath : pathlib.Path
            Object configuration file
        """

        # ----------------------------------------
        #   Check api parameters
        # ----------------------------------------

        if type(api) is None:
            raise TypeError("Wrong type for api, expected geode.api.API")

        self.api = api

        # ----------------------------------------
        #   Variables
        # ----------------------------------------

        # Store console instance
        self.console = console

        # Store an uniq identifier
        self.id = generate_identifier(filepath)

        # Store full game path
        self.path = filepath

        # Store game environment keys
        self.environment = dict()

        # Store game installation date
        self.installed = datetime.fromtimestamp(filepath.stat().st_ctime).date()

        # Store fullscreen status
        self.fullscreen = False

        # ----------------------------------------
        #   Initialization
        # ----------------------------------------

        # Initialize metadata
        self.__init_game()


    def __init_game(self):
        """ Initialize game metadata from database content
        """

        # Retrieve game metadata from database
        metadata = self.api.get_database().select("games", "*",
            where=[("filename", Database.Condition.EQUAL, self.path.name)])

        # This games owned some data in database
        if len(metadata) > 0:
            metadata = metadata[0]

        # ----------------------------------------
        #   Initialization
        # ----------------------------------------

        # Retrieve time from a string (HH:MM:SS.MS)
        regex = re_compile(r"(\d+):(\d+):(\d+)[\.\d*]?")

        for key, value in self.attributes:
            index = self.attributes.index((key, value))

            if len(metadata) > 0 and len(metadata) >= index:

                # ----------------------------------------
                #   Manage specific variable
                # ----------------------------------------

                if type(value) is bool:
                    value = bool(metadata[index])

                elif type(value) is int:
                    value = int(metadata[index])

                elif type(value) is list:

                    if len(metadata[index]) > 0:
                        value = metadata[index].split(';')

                elif type(value) is date:

                    # ISO 8601 format (YYYY-MM-DD)
                    if len(metadata[index]) == 10:
                        year, month, day = metadata[index].split('-')

                        value = date(int(year), int(month), int(day))

                    # Remove date with an unknown format
                    else:
                        value = None

                elif type(value) is timedelta:
                    result = regex.match(metadata[index])

                    if result is not None:
                        hours, minutes, seconds = result.groups()

                        value = timedelta(
                            hours=int(hours),
                            minutes=int(minutes),
                            seconds=int(seconds))

                    else:
                        value = None

                elif len(metadata[index]) > 0:
                    value = metadata[index]

                # ----------------------------------------
                #   Manage specific constant
                # ----------------------------------------

                if type(value) is str and len(value) > 0:

                    if key == "cover":
                        value = Path(expanduser(value))

                    elif key == "emulator":
                        value = self.api.get_emulator(value)

            # Avoid to have a silly date
            elif type(value) is date:
                value = None

            setattr(self, key, value)

        # Check if a name is unavailable
        if self.name is None or len(self.name) == 0:
            self.name = self.path.stem

        # ----------------------------------------
        #   Environment keys
        # ----------------------------------------

        config = self.api.get_environment()

        if config.has_section(self.id):

            for key, value in config.items(self.id):
                self.environment[key] = value


    def __str__(self):
        """ Print object as string

        Returns
        -------
        str
            Formatted object data as string
        """

        return self.id


    def __get_contents(self, choice):
        """ Retrieve a game folder contents based on an emulator pattern

        Parameters
        ----------
        choice : str
            Emulator pattern attribute (savestate or snapshot)

        Returns
        -------
        list
            Folder contents
        """

        emulator = self.console.emulator

        if self.emulator is not None:
            emulator = self.emulator

        path = getattr(emulator, choice, None)

        if path is not None:
            keys = {
                "rom-name": self.path.stem,
                "rom-name-lower": self.path.stem.lower(),
                "rom-name-upper": self.path.stem.upper(),
                "rom-file": self.path,
                "rom-folder": self.path.parent,
                "gameid": self.gameid }

            pattern = str(path)

            # Avoid to manage empty path
            if len(pattern) > 0:

                for key, value in keys.items():
                    substring = "<%s>" % str(key)

                    # Avoid to continue if wanted key as no value
                    if substring in pattern and value is None:
                        return list()

                    pattern = pattern.replace(substring, str(value))

                # Set a pathlib.Path to allow glob filter
                folder = Path(dirname(pattern))

                # A glob pattern use brackets to match string, so we replace the
                # brackets from a filename with a joker to be sure we retrieve
                # all the corresponding files
                pattern = basename(pattern).replace('[', '?').replace(']', '?')

                return list(sorted(folder.glob(pattern)))

        return list()


    @property
    def snapshots(self):
        """ Retrieve snapshots files list based on emulator pattern

        Returns
        -------
        list
            Snapshots files list
        """

        return self.__get_contents("snapshot")


    @property
    def savestates(self):
        """ Retrieve savestates files list based on emulator pattern

        Returns
        -------
        list
            Savestate files list
        """

        return self.__get_contents("savestate")


    @property
    def command(self):
        """ Generate launch command from game metadata

        Returns
        -------
        list
            Generated command as arguments list
        """

        emulator = self.console.emulator

        if self.emulator is not None:
            emulator = self.emulator

        if not emulator.exists:
            raise OSError(2, "Cannot found binary: %s" % emulator.binary)

        # ----------------------------------------
        #   Retrieve default parameters
        # ----------------------------------------

        arguments = shlex_split(emulator.binary)

        # Retrieve default or specific arguments
        if self.arguments is not None:
            arguments.append(self.arguments)
        elif emulator.default is not None:
            arguments.append(emulator.default)

        # Retrieve fullscreen mode
        if self.fullscreen and emulator.fullscreen is not None:
            arguments.append(emulator.fullscreen)
        elif not self.fullscreen and emulator.windowed is not None:
            arguments.append(emulator.windowed)

        # ----------------------------------------
        #   Replace pattern substitutes
        # ----------------------------------------

        command = ' '.join(arguments).strip()

        need_gamefile = True

        keys = {
            "configuration": emulator.configuration,
            "rom-name": self.path.stem,
            "rom-name-lower": self.path.stem.lower(),
            "rom-name-upper": self.path.stem.upper(),
            "rom-file": self.path,
            "rom-folder": self.path.parent,
            "gameid": self.gameid }

        for key, value in keys.items():
            substring = "<%s>" % key

            if value is not None and substring in command:
                command = command.replace(substring, str(value))

                if key in ("rom-name", "rom-file", "rom-folder"):
                    need_gamefile = False

        # ----------------------------------------
        #   Generate subprocess compatible command
        # ----------------------------------------

        arguments = shlex_split(command)

        if need_gamefile:
            arguments.append(str(self.path))

        return arguments


    def run(self):
        """ Launch a game

        This function avoit to save metadata into database to allow
        multi-threading
        """

        log = self.api.get_storage_path().joinpath("logs", "%s.log" % self.id)

        # Generate logs folder if missing
        if not log.parent.exists():
            log.parent.mkdir(0o755, True)

        # ----------------------------------------
        #   Check environment
        # ----------------------------------------

        emulator = self.console.emulator

        if self.emulator is not None:
            emulator = self.emulator

        # Get a copy of current environment
        environment = environ.copy()

        # Check if game emulator has specific environment variable
        for key, value in emulator.environment.items():
            environment[key] = value

        # Check if game console has specific environment variable
        for key, value in self.console.environment.items():
            environment[key] = value

        # Check if current game has specific environment variable
        for key, value in self.environment.items():
            environment[key] = value

        # ----------------------------------------
        #   Launch game
        # ----------------------------------------

        # Start game timer
        started = datetime.now()

        # Open log file to write stdout
        with open(log, 'w') as pipe:

            # Start game process
            with Popen(self.command, stdout=pipe, stderr=pipe,
                env=environment, universal_newlines=True) as self.process:

                self.api.log.info(
                    "Launch %s with PID %d" % (self.name, self.process.pid))
                self.api.log.debug(
                    "Command: %s" % ' '.join(self.process.args))

                self.api.log.info("Log to %s" % str(log))

                # Write into log file until game stop
                while self.process.stdout is not None:
                    pipe.write(self.process.stdout.read(1))

        # Stop timer and retrieve delta
        delta = datetime.now() - started

        self.api.log.info("Close %s" % self.name)

        # ----------------------------------------
        #   Update metadata
        # ----------------------------------------

        self.played += 1
        self.play_time += delta
        self.last_launch_date = date.today()
        self.last_launch_time = delta


    def copy(self, filepath):
        """ Copy game instance

        Parameters
        ----------
        filepath : pathlib.Path
            Object configuration file

        Returns
        -------
        geode.lib.game.Game
            New game instance
        """

        game = deepcopy(self)

        # Generate a new identifier
        game.id = generate_identifier(filepath)

        # Retrieve name from filename
        game.name = filepath.stem

        # Store filepath
        game.path = filepath

        return game


    def save(self):
        """ Save game instance to database
        """

        database = self.api.get_database()

        # Retrieve game metadata from database
        metadata = database.select("games", "filename",
            where=[("filename", Database.Condition.EQUAL, self.path.name)])

        # Generate a dictionary with database scheme format
        data = dict()

        for key, value in self.attributes:
            value = getattr(self, key, value)

            # ----------------------------------------
            #   Manage specific variable
            # ----------------------------------------

            if value is None:
                value = str()

            elif type(value) is bool:
                value = int(value)

            elif type(value) is list:
                value = ';'.join(value)

            elif type(value) is Path:
                value = value.name

            elif type(value) is Emulator:
                value = value.id

            # ----------------------------------------
            #   Manage specific constant
            # ----------------------------------------

            if key == "filename":
                value = self.path.name

            data[key] = str(value)

        # Update entry from database
        if len(metadata) > 0:
            database.update("games", data=data,
                where=[("filename", Database.Condition.EQUAL, self.path.name)])

        # Generate a new entry into database
        else:
            database.insert("games", data=data)

        # ----------------------------------------
        #   Environment keys
        # ----------------------------------------

        config = self.api.get_environment()

        if len(self.environment) > 0:

            if not config.has_section(self.id):
                config.add_section(self.id)

            for key, value in self.environment.items():
                config.modify(self.id, key, value)

        elif config.has_section(self.id):
            config.remove_section(self.id)

        config.update()


    def delete(self):
        """ Delete game instance from database
        """

        database = self.api.get_database()

        database.delete("games",
            where=[("filename", Database.Condition.EQUAL, self.path.name)])


    def clear_metadata(self):
        """ Clear metadata
        """

        for key, value in self.attributes:
            setattr(self, key, value)

        # Generate a new identifier
        self.id = generate_identifier(self.path)

        # Retrieve name from filename
        self.name = self.path.stem

        # Avoid to have a silly date
        self.last_launch_date = None


    def reset_metadata(self):
        """ Reset game metadata from database
        """

        self.__init_game()

Class variables

var attributes

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

Instance variables

var command

Generate launch command from game metadata

Returns

list
Generated command as arguments list
Expand source code
@property
def command(self):
    """ Generate launch command from game metadata

    Returns
    -------
    list
        Generated command as arguments list
    """

    emulator = self.console.emulator

    if self.emulator is not None:
        emulator = self.emulator

    if not emulator.exists:
        raise OSError(2, "Cannot found binary: %s" % emulator.binary)

    # ----------------------------------------
    #   Retrieve default parameters
    # ----------------------------------------

    arguments = shlex_split(emulator.binary)

    # Retrieve default or specific arguments
    if self.arguments is not None:
        arguments.append(self.arguments)
    elif emulator.default is not None:
        arguments.append(emulator.default)

    # Retrieve fullscreen mode
    if self.fullscreen and emulator.fullscreen is not None:
        arguments.append(emulator.fullscreen)
    elif not self.fullscreen and emulator.windowed is not None:
        arguments.append(emulator.windowed)

    # ----------------------------------------
    #   Replace pattern substitutes
    # ----------------------------------------

    command = ' '.join(arguments).strip()

    need_gamefile = True

    keys = {
        "configuration": emulator.configuration,
        "rom-name": self.path.stem,
        "rom-name-lower": self.path.stem.lower(),
        "rom-name-upper": self.path.stem.upper(),
        "rom-file": self.path,
        "rom-folder": self.path.parent,
        "gameid": self.gameid }

    for key, value in keys.items():
        substring = "<%s>" % key

        if value is not None and substring in command:
            command = command.replace(substring, str(value))

            if key in ("rom-name", "rom-file", "rom-folder"):
                need_gamefile = False

    # ----------------------------------------
    #   Generate subprocess compatible command
    # ----------------------------------------

    arguments = shlex_split(command)

    if need_gamefile:
        arguments.append(str(self.path))

    return arguments
var savestates

Retrieve savestates files list based on emulator pattern

Returns

list
Savestate files list
Expand source code
@property
def savestates(self):
    """ Retrieve savestates files list based on emulator pattern

    Returns
    -------
    list
        Savestate files list
    """

    return self.__get_contents("savestate")
var snapshots

Retrieve snapshots files list based on emulator pattern

Returns

list
Snapshots files list
Expand source code
@property
def snapshots(self):
    """ Retrieve snapshots files list based on emulator pattern

    Returns
    -------
    list
        Snapshots files list
    """

    return self.__get_contents("snapshot")

Methods

def clear_metadata(self)

Clear metadata

Expand source code
def clear_metadata(self):
    """ Clear metadata
    """

    for key, value in self.attributes:
        setattr(self, key, value)

    # Generate a new identifier
    self.id = generate_identifier(self.path)

    # Retrieve name from filename
    self.name = self.path.stem

    # Avoid to have a silly date
    self.last_launch_date = None
def copy(self, filepath)

Copy game instance

Parameters

filepath : pathlib.Path
Object configuration file

Returns

Game
New game instance
Expand source code
def copy(self, filepath):
    """ Copy game instance

    Parameters
    ----------
    filepath : pathlib.Path
        Object configuration file

    Returns
    -------
    geode.lib.game.Game
        New game instance
    """

    game = deepcopy(self)

    # Generate a new identifier
    game.id = generate_identifier(filepath)

    # Retrieve name from filename
    game.name = filepath.stem

    # Store filepath
    game.path = filepath

    return game
def delete(self)

Delete game instance from database

Expand source code
def delete(self):
    """ Delete game instance from database
    """

    database = self.api.get_database()

    database.delete("games",
        where=[("filename", Database.Condition.EQUAL, self.path.name)])
def reset_metadata(self)

Reset game metadata from database

Expand source code
def reset_metadata(self):
    """ Reset game metadata from database
    """

    self.__init_game()
def run(self)

Launch a game

This function avoit to save metadata into database to allow multi-threading

Expand source code
def run(self):
    """ Launch a game

    This function avoit to save metadata into database to allow
    multi-threading
    """

    log = self.api.get_storage_path().joinpath("logs", "%s.log" % self.id)

    # Generate logs folder if missing
    if not log.parent.exists():
        log.parent.mkdir(0o755, True)

    # ----------------------------------------
    #   Check environment
    # ----------------------------------------

    emulator = self.console.emulator

    if self.emulator is not None:
        emulator = self.emulator

    # Get a copy of current environment
    environment = environ.copy()

    # Check if game emulator has specific environment variable
    for key, value in emulator.environment.items():
        environment[key] = value

    # Check if game console has specific environment variable
    for key, value in self.console.environment.items():
        environment[key] = value

    # Check if current game has specific environment variable
    for key, value in self.environment.items():
        environment[key] = value

    # ----------------------------------------
    #   Launch game
    # ----------------------------------------

    # Start game timer
    started = datetime.now()

    # Open log file to write stdout
    with open(log, 'w') as pipe:

        # Start game process
        with Popen(self.command, stdout=pipe, stderr=pipe,
            env=environment, universal_newlines=True) as self.process:

            self.api.log.info(
                "Launch %s with PID %d" % (self.name, self.process.pid))
            self.api.log.debug(
                "Command: %s" % ' '.join(self.process.args))

            self.api.log.info("Log to %s" % str(log))

            # Write into log file until game stop
            while self.process.stdout is not None:
                pipe.write(self.process.stdout.read(1))

    # Stop timer and retrieve delta
    delta = datetime.now() - started

    self.api.log.info("Close %s" % self.name)

    # ----------------------------------------
    #   Update metadata
    # ----------------------------------------

    self.played += 1
    self.play_time += delta
    self.last_launch_date = date.today()
    self.last_launch_time = delta
def save(self)

Save game instance to database

Expand source code
def save(self):
    """ Save game instance to database
    """

    database = self.api.get_database()

    # Retrieve game metadata from database
    metadata = database.select("games", "filename",
        where=[("filename", Database.Condition.EQUAL, self.path.name)])

    # Generate a dictionary with database scheme format
    data = dict()

    for key, value in self.attributes:
        value = getattr(self, key, value)

        # ----------------------------------------
        #   Manage specific variable
        # ----------------------------------------

        if value is None:
            value = str()

        elif type(value) is bool:
            value = int(value)

        elif type(value) is list:
            value = ';'.join(value)

        elif type(value) is Path:
            value = value.name

        elif type(value) is Emulator:
            value = value.id

        # ----------------------------------------
        #   Manage specific constant
        # ----------------------------------------

        if key == "filename":
            value = self.path.name

        data[key] = str(value)

    # Update entry from database
    if len(metadata) > 0:
        database.update("games", data=data,
            where=[("filename", Database.Condition.EQUAL, self.path.name)])

    # Generate a new entry into database
    else:
        database.insert("games", data=data)

    # ----------------------------------------
    #   Environment keys
    # ----------------------------------------

    config = self.api.get_environment()

    if len(self.environment) > 0:

        if not config.has_section(self.id):
            config.add_section(self.id)

        for key, value in self.environment.items():
            config.modify(self.id, key, value)

    elif config.has_section(self.id):
        config.remove_section(self.id)

    config.update()