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()