1
0
mirror of https://github.com/gryf/e-uae-wrapper.git synced 2026-02-07 17:45:45 +01:00

Initial import

This commit is contained in:
2018-02-15 17:49:24 +01:00
commit 9e07e0b53c
8 changed files with 540 additions and 0 deletions

24
LICENSE Normal file
View File

@@ -0,0 +1,24 @@
Copyright (c) 2018, Roman Dobosz
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the organization nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL ROMAN DOBOSZ BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

19
README.rst Normal file
View File

@@ -0,0 +1,19 @@
=============
E-UAE Wrapper
=============
This little utility is a wrapper on old E-UAE_ emulator, to help divide
configuration to common and specific per ``.uaerc`` configuration, and combine
them together during runtime.
Global (a common) config file will reside on ``XDG_CONFIG_HOME/e-uaerc``, and
supports following options:
(TODO)
License
=======
This work is licensed on 3-clause BSD license. See LICENSE file for details.
.. _e-uae: http://www.rcdrummond.net/uae/

View File

@@ -0,0 +1 @@
WRAPPER_KEY = 'wrapper'

244
e_uae_wrapper/base.py Normal file
View File

@@ -0,0 +1,244 @@
"""
Base class for all wrapper modules
"""
import logging
import os
import shutil
import sys
import tempfile
from e_uae_wrapper import utils
from e_uae_wrapper import path
class Base(object):
"""
Base class for wrapper modules
"""
def __init__(self, conf_file, config):
"""
Params:
config: parsed lines combined from global and local config
"""
self.config = config
self.dir = None
self.save_filename = None
self.conf_file = conf_file
def run(self):
"""
Main function which accepts config file for e-uae
It will do as follows:
- set needed full path for asset files
- extract archive file
- copy configuration
- [copy save if exists]
- run the emulation
- archive save state
"""
if not self._validate_options():
return False
self.dir = tempfile.mkdtemp()
self._interpolate_options()
self._set_assets_paths()
return True
def clean(self):
"""Remove temporary file"""
if self.dir:
shutil.rmtree(self.dir)
return
def _set_assets_paths(self):
"""
Set full paths for archive file (without extension) and for save state
archive file
"""
conf_abs_dir = os.path.dirname(os.path.abspath(self.conf_file))
conf_base = os.path.basename(self.conf_file)
conf_base = os.path.splitext(conf_base)[0]
# set optional save_state
arch_ext = utils.get_arch_ext(self.config.get('wrapper_archiver'))
if arch_ext:
self.save_filename = os.path.join(conf_abs_dir, conf_base +
'_save' + arch_ext)
def _copy_conf(self):
"""copy provided configuration as .uaerc"""
shutil.copy(self.conf_file, self.dir)
os.rename(os.path.join(self.dir, os.path.basename(self.conf_file)),
os.path.join(self.dir, '.uaerc'))
return True
def _run_emulator(self):
"""execute e-uae"""
curdir = os.path.abspath('.')
os.chdir(self.dir)
utils.run_command(['e-uae'])
os.chdir(curdir)
return True
def _get_title(self):
"""
Return the title if found in config. As a fallback archive file
name will be used as title.
"""
title = ''
gui_msg = self.config.get('wrapper_gui_msg', '0')
if gui_msg == '1':
title = self.config.get('title')
if not title:
title = self.config['wrapper_archive']
return title
def _save_save(self):
"""
Get the saves from emulator and store it where configuration is placed
"""
if self.config.get('wrapper_save_state', '0') != '1':
return True
os.chdir(self.dir)
save_path = self._get_saves_dir()
if not save_path:
return True
if os.path.exists(self.save_filename):
os.unlink(self.save_filename)
curdir = os.path.abspath('.')
if not utils.create_archive(self.save_filename, '', [save_path]):
logging.error('Error: archiving save state failed.')
os.chdir(curdir)
return False
os.chdir(curdir)
return True
def _load_save(self):
"""
Put the saves (if exists) to the temp directory.
"""
if self.config.get('wrapper_save_state', '0') != '1':
return True
if not os.path.exists(self.save_filename):
return True
curdir = os.path.abspath('.')
os.chdir(self.dir)
utils.extract_archive(self.save_filename)
os.chdir(curdir)
return True
def _get_saves_dir(self):
"""
Return path to save state directory or None in cases:
- there is no save state dir set relative to copied config file
- save state dir is set globally
- save state dir is set relative to the config file
- save state dir doesn't exists
Note, that returned path is relative not absolute
"""
if not self.config.get('save_states_dir'):
return None
if self.config['save_states_dir'].startswith('$WRAPPER') and \
'..' not in self.config['save_states_dir']:
save = self.config['save_states_dir'].replace('$WRAPPER/', '')
else:
return None
save_path = os.path.join(self.dir, save)
if not os.path.exists(save_path) or not os.path.isdir(save_path):
return None
if save.endswith('/'):
save = save[:-1]
return save
def _interpolate_options(self):
"""
Search and replace values for options which contains {{ and }}
markers for replacing them with correpsonding calculated values
"""
for key, val in self.config.items():
print key, val
def _validate_options(self):
"""Validate mandatory options"""
if 'wrapper' not in self.config:
logging.error("Configuration lacks of required `wrapper' option.")
return False
if self.config.get('wrapper_save_state', '0') == '0':
return True
if 'wrapper_archiver' not in self.config:
logging.error("Configuration lacks of required "
"`wrapper_archiver' option.")
return False
if not path.which(self.config['wrapper_archiver']):
logging.error("Cannot find archiver `%s'.",
self.config['wrapper_archiver'])
return False
return True
class ArchiveBase(Base):
"""
Base class for archive based wrapper modules
"""
def __init__(self, conf_path, config):
"""
Params:
conf_file: a relative path to provided configuration file
fsuae_options: is an CmdOption object created out of command line
parameters
config: is config dictionary created out of config file
"""
super(ArchiveBase, self).__init__(conf_path, config)
self.arch_filepath = None
def _set_assets_paths(self):
"""
Set full paths for archive file (without extension) and for save state
archive file
"""
super(ArchiveBase, self)._set_assets_paths()
conf_abs_dir = os.path.dirname(os.path.abspath(self.conf_file))
arch = self.config.get('wrapper_archive')
if arch:
if os.path.isabs(arch):
self.arch_filepath = arch
else:
self.arch_filepath = os.path.join(conf_abs_dir, arch)
def _extract(self):
"""Extract archive to temp dir"""
title = self._get_title()
curdir = os.path.abspath('.')
os.chdir(self.dir)
result = utils.extract_archive(self.arch_filepath, title)
os.chdir(curdir)
return result
def _validate_options(self):
validation_result = super(ArchiveBase, self)._validate_options()
if 'wrapper_archive' not in self.config:
sys.stderr.write("Configuration lacks of required "
"`wrapper_archive' option.\n")
validation_result = False
return validation_result

26
e_uae_wrapper/plain.py Normal file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Simple class for executing e-uae with specified parameters. This is a
failsafe class for running e-uae.
"""
from e_uae_wrapper import base
from e_uae_wrapper import utils
class Wrapper(base.Base):
"""Simple class for running e-uae"""
def run(self):
"""
Main function which run e-uae
"""
self._run_emulator()
def _run_emulator(self):
"""execute e-uae"""
utils.run_command(['e-uae', self.conf_file])
def clean(self):
"""Do the cleanup. Here - just do nothing"""
return

123
e_uae_wrapper/utils.py Normal file
View File

@@ -0,0 +1,123 @@
"""
Misc utilities
"""
import collections
import logging
import os
import subprocess
try:
import configparser
except ImportError:
import ConfigParser as configparser
from e_uae_wrapper import file_archive
def load_conf(conf_file):
"""
Read global config and provided config file and return dict with combined
options."""
conf = _get_common_config()
local_conf = collections.OrderedDict()
with open(conf_file) as fobj:
for line in fobj:
key, val = line.strip().split('=')
if key in local_conf:
raise Exception('%s already in conf' % key)
local_conf[key] = val
conf.update(local_conf)
return conf
def operate_archive(arch_name, operation, text, params):
"""
Create archive from contents of current directory
"""
archiver = file_archive.get_archiver(arch_name)
if archiver is None:
return False
res = False
if operation == 'extract':
res = archiver.extract(arch_name)
if operation == 'create':
res = archiver.create(arch_name, params)
return res
def create_archive(arch_name, title='', params=None):
"""
Create archive from contents of current directory
"""
msg = ''
if title:
msg = "Creating archive for `%s'. Please be patient" % title
return operate_archive(arch_name, 'create', msg, params)
def extract_archive(arch_name, title='', params=None):
"""
Extract provided archive to current directory
"""
msg = ''
if title:
msg = "Extracting files for `%s'. Please be patient" % title
return operate_archive(arch_name, 'extract', msg, params)
def run_command(cmd):
"""
Run provided command. Return true if command execution returns zero exit
code, false otherwise. If cmd is not a list, there would be an attempt to
split it up for subprocess call method. May throw exception if cmd is not
a list neither a string.
"""
if not isinstance(cmd, list):
cmd = cmd.split()
logging.debug("Executing `%s'.", " ".join(cmd))
code = subprocess.call(cmd)
if code != 0:
logging.error('Command `%s` returned non 0 exit code.', cmd[0])
return False
return True
def _get_common_config():
"""
Try to find common configuration file and return data as a dict.
File will be gather from $XDG_CONFIG_HOME/e-uaerc, which ususally is
~/.config/e-uaerc
"""
parser = configparser.SafeConfigParser()
xdg_conf = os.getenv('XDG_CONFIG_HOME', os.path.expanduser('~/.config'))
conf_path = os.path.join(xdg_conf, 'e-uae.ini')
try:
parser.read(conf_path)
except configparser.ParsingError:
# Configuration syntax is wrong
return {}
section = parser.sections()[0]
conf = collections.OrderedDict()
for option in parser.options(section):
if option in ['wrapper_rom_path']:
conf[option] = parser.get(section, option)
return conf
def get_arch_ext(archiver_name):
"""Return extension for the archiver"""
return file_archive.Archivers.get_extension_by_name(archiver_name)

87
e_uae_wrapper/wrapper.py Normal file
View File

@@ -0,0 +1,87 @@
#!/usr/bin/env python
"""
Wrapper for e-uae to perform some actions before and or after running the
emulator, if appropriate option is enabled.
"""
import argparse
import importlib
import logging
import os
import sys
from e_uae_wrapper import utils
from e_uae_wrapper import WRAPPER_KEY
def setup_logger(args):
"""Setup logger format and level"""
level = logging.WARNING
if args.quiet:
level = logging.ERROR
if args.quiet > 1:
level = logging.CRITICAL
if args.verbose:
level = logging.INFO
if args.verbose > 1:
level = logging.DEBUG
logging.basicConfig(level=level,
format="%(asctime)s %(levelname)s: %(message)s")
def parse_args():
"""
Look out for config file and for config options which would be blindly
passed to e-uae.
"""
parser = argparse.ArgumentParser()
parser.add_argument('config', help='Configuration file for e-uae.')
parser.add_argument('-v', '--verbose', help='Be verbose. Adding more "v" '
'will increase verbosity', action="count",
default=None)
parser.add_argument('-q', '--quiet', help='Be quiet. Adding more "q" will'
' decrease verbosity', action="count", default=None)
args = parser.parse_args()
setup_logger(args)
logging.debug("args: %s", args)
return args
def run():
"""run wrapper module"""
args = parse_args()
configuration = utils.load_conf(args.config)
if not configuration:
logging.error('Error: Configuration file have syntax issues')
sys.exit(2)
wrapper_module = configuration.get(WRAPPER_KEY, 'plain')
try:
wrapper = importlib.import_module('e_uae_wrapper.' +
wrapper_module)
except ImportError:
logging.error("Error: provided wrapper module: `%s' doesn't "
"exists.", wrapper_module)
sys.exit(3)
runner = wrapper.Wrapper(os.path.abspath(args.config), configuration)
try:
exit_code = runner.run()
finally:
runner.clean()
if not exit_code:
sys.exit(4)
if __name__ == "__main__":
run()

16
script/e-uae-wrapper Executable file
View File

@@ -0,0 +1,16 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Run e-uae emulator with some configuration goodies :)
"""
from e_uae_wrapper import wrapper
def main():
"""run wrapper"""
wrapper.run()
if __name__ == "__main__":
main()