mirror of
https://github.com/gryf/fs-uae-wrapper.git
synced 2026-02-01 21:45:54 +01:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7b0ef15eae | |||
| 59bd1b6029 | |||
| f5e6471555 | |||
| 4c61c3d7ea | |||
| 7e3d68624f | |||
| f311605019 | |||
| 418e480fb5 | |||
| 118d758ec7 | |||
| 3b597e34ee | |||
| 7b40974779 | |||
| b4ab8ac4f5 | |||
| 8d8d38d5c0 | |||
| bd0aa3dee4 | |||
| d2a9f39fd9 | |||
| f1f64cf4d4 | |||
| a5606272cd | |||
| 463f6ed705 | |||
| 148d28dac2 | |||
| 60139d1728 | |||
| 68b1f2a787 |
33
.github/workflows/test.yml
vendored
Normal file
33
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# This workflow will install Python dependencies, run tests and lint with a single version of Python
|
||||||
|
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
|
||||||
|
|
||||||
|
name: Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "master" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "master" ]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Set up Python 3.10
|
||||||
|
uses: actions/setup-python@v3
|
||||||
|
with:
|
||||||
|
python-version: "3.10"
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install flake8 pytest tox coverage pytest-cov pytest-pep8
|
||||||
|
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
||||||
|
- name: Test with pytest
|
||||||
|
run: |
|
||||||
|
tox
|
||||||
11
.travis.yml
11
.travis.yml
@@ -1,11 +0,0 @@
|
|||||||
language: python
|
|
||||||
env:
|
|
||||||
- TOXENV=py27
|
|
||||||
- TOXENV=py27-flake8
|
|
||||||
- TOXENV=py3
|
|
||||||
- TOXENV=py3-flake8
|
|
||||||
install: pip install tox
|
|
||||||
script: tox
|
|
||||||
before_install:
|
|
||||||
- sudo apt-get update
|
|
||||||
- sudo apt-get install -y python-tk python3-tk
|
|
||||||
235
README.rst
235
README.rst
@@ -2,8 +2,8 @@
|
|||||||
FS-UAE Wrapper
|
FS-UAE Wrapper
|
||||||
==============
|
==============
|
||||||
|
|
||||||
.. image:: https://travis-ci.org/gryf/fs-uae-wrapper.svg?branch=master
|
.. image:: https://github.com/gryf/fs-uae-wrapper/workflows/Test/badge.svg
|
||||||
:target: https://travis-ci.org/gryf/fs-uae-wrapper
|
:target: https://github.com/gryf/fs-uae-wrapper/actions?query=workflow%3ATest+event%3Apush+branch%3Amaster
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/v/fs-uae-wrapper.svg
|
.. image:: https://img.shields.io/pypi/v/fs-uae-wrapper.svg
|
||||||
:target: https://pypi.python.org/pypi/fs-uae-wrapper
|
:target: https://pypi.python.org/pypi/fs-uae-wrapper
|
||||||
@@ -137,6 +137,7 @@ Currently, couple of wrapper modules are available:
|
|||||||
- cd32
|
- cd32
|
||||||
- archive
|
- archive
|
||||||
- savestate
|
- savestate
|
||||||
|
- whdload
|
||||||
|
|
||||||
plain
|
plain
|
||||||
-----
|
-----
|
||||||
@@ -157,8 +158,8 @@ Options used:
|
|||||||
|
|
||||||
* ``wrapper`` (required) with ``cd32`` as an value
|
* ``wrapper`` (required) with ``cd32`` as an value
|
||||||
* ``wrapper_archive`` (required) path to the archive with CD32 iso/cue/wav
|
* ``wrapper_archive`` (required) path to the archive with CD32 iso/cue/wav
|
||||||
* ``wrapper_archiver`` (conditionally required) archiver to use for storage
|
* ``wrapper_archiver`` (optional) archiver to use for storage save state -
|
||||||
save state
|
default ``7z``.
|
||||||
* ``wrapper_gui_msg`` (optional) if set to "1", will display a graphical
|
* ``wrapper_gui_msg`` (optional) if set to "1", will display a graphical
|
||||||
message during extracting files
|
message during extracting files
|
||||||
* ``wrapper_save_state`` (optional) if set to "1", will load/archive save state
|
* ``wrapper_save_state`` (optional) if set to "1", will load/archive save state
|
||||||
@@ -175,7 +176,7 @@ fragment of configuration file is saved as ``ChaosEngine.fs-uae``:
|
|||||||
[config]
|
[config]
|
||||||
wrapper = cd32
|
wrapper = cd32
|
||||||
wrapper_archive = ChaosEngine.7z
|
wrapper_archive = ChaosEngine.7z
|
||||||
wrapper_archiver = 7z
|
wrapper_archiver = zip
|
||||||
wrapper_gui_msg = 1
|
wrapper_gui_msg = 1
|
||||||
|
|
||||||
amiga_model = CD32
|
amiga_model = CD32
|
||||||
@@ -219,10 +220,12 @@ archive
|
|||||||
Options used:
|
Options used:
|
||||||
|
|
||||||
* ``wrapper`` (required) with ``archive`` as an value
|
* ``wrapper`` (required) with ``archive`` as an value
|
||||||
* ``wrapper_archive`` (required) path to the archive with assets (usually means
|
* ``wrapper_archive`` (optional) path to the archive with assets (usually means
|
||||||
whole system directories, floppies or hard disk images)
|
whole system directories, floppies or hard disk images), defaults to same
|
||||||
* ``wrapper_archiver`` (conditionally required) archiver to use for storage
|
name as configuration file with some detected archive extension. Note, that
|
||||||
save state
|
name is case sensitive
|
||||||
|
* ``wrapper_archiver`` (optional) archiver to use for storage save state -
|
||||||
|
default ``7z``.
|
||||||
* ``wrapper_gui_msg`` (optional) if set to "1", will display a graphical
|
* ``wrapper_gui_msg`` (optional) if set to "1", will display a graphical
|
||||||
message during extracting files
|
message during extracting files
|
||||||
* ``wrapper_persist_data`` (optional) if set to "1", will compress (possibly
|
* ``wrapper_persist_data`` (optional) if set to "1", will compress (possibly
|
||||||
@@ -277,7 +280,8 @@ savestate
|
|||||||
Options used:
|
Options used:
|
||||||
|
|
||||||
* ``wrapper`` (required) with ``archive`` as an value
|
* ``wrapper`` (required) with ``archive`` as an value
|
||||||
* ``wrapper_archiver`` (required) archiver to use for storage save state
|
* ``wrapper_archiver`` (optional) archiver to use for storage save state -
|
||||||
|
default ``7z``.
|
||||||
|
|
||||||
This module is primarily used to run emulator with read only media attached
|
This module is primarily used to run emulator with read only media attached
|
||||||
(like images of floppies or uncompressed CD-ROMs) and its purpose is to
|
(like images of floppies or uncompressed CD-ROMs) and its purpose is to
|
||||||
@@ -311,6 +315,212 @@ The steps would be as follows:
|
|||||||
- optionally create archive with save state (if save state directory place is
|
- optionally create archive with save state (if save state directory place is
|
||||||
*not* a global one)
|
*not* a global one)
|
||||||
|
|
||||||
|
whdload
|
||||||
|
-------
|
||||||
|
|
||||||
|
Options used:
|
||||||
|
|
||||||
|
* ``wrapper`` (required) with ``whdload`` as an value
|
||||||
|
* ``wrapper_whdload_base`` (required) path to the whdload base system. Usually
|
||||||
|
it's minimal system containing at least whdload executables in C, and config
|
||||||
|
in S. Read on below for further details.
|
||||||
|
* ``wrapper_whdload_options`` (optional) this option will replace the line in
|
||||||
|
``s:whdload-startup`` with specific ``whdload`` options for certain slave.
|
||||||
|
For reference look at WHDLoad documentation and/or on ``s:WHDLoad.prefs``.
|
||||||
|
Note, that ``Slave=`` option must not be used.
|
||||||
|
* ``wrapper_archive`` (optional) path to the whdload archive, defaults to same
|
||||||
|
name as configuration file with some detected archive extension. Note, that
|
||||||
|
name is case sensitive
|
||||||
|
|
||||||
|
This module is solely used with whdload distributed games (not just whdload
|
||||||
|
slave files, but whole games, which can be found on several places on the
|
||||||
|
internet).
|
||||||
|
|
||||||
|
Base image
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
To make it work, first the absolute minimal image need to contain following
|
||||||
|
structure:
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
.
|
||||||
|
├── C
|
||||||
|
│  ├── DIC
|
||||||
|
│  ├── Execute
|
||||||
|
│  ├── Patcher
|
||||||
|
│  ├── RawDIC
|
||||||
|
│  ├── SetPatch
|
||||||
|
│  ├── WHDLoad
|
||||||
|
│  └── WHDLoadCD32
|
||||||
|
└── S
|
||||||
|
├── startup-sequence
|
||||||
|
└── WHDLoad.prefs
|
||||||
|
|
||||||
|
where the minimum dependences are:
|
||||||
|
|
||||||
|
- ``Excecute`` from your copy of Workbench
|
||||||
|
- `WHDLoad`_ 18.9
|
||||||
|
- `SetPatch`_ 43.6
|
||||||
|
|
||||||
|
and the ``S/startup-sequence`` should at least contain:
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
setpatch QUIET
|
||||||
|
|
||||||
|
IF EXISTS S:whdload-startup
|
||||||
|
Execute S:whdload-startup
|
||||||
|
EndIF
|
||||||
|
|
||||||
|
To leverage more pleasant UX, additionally those bits should be installed (or -
|
||||||
|
copied into base image filesystem):
|
||||||
|
|
||||||
|
- ``Assign`` and whatever commands you'll be use in scripts from your copy of
|
||||||
|
Workbench
|
||||||
|
- `uaequit`_ - this will allow to quit emulator, after quiting game
|
||||||
|
- `kgiconload`_ - tool for reading icon and executing *default tool* with
|
||||||
|
optionally defined tool types as parameters (in this case: WHDLoad)
|
||||||
|
- `SKick`_ optionally - for kickstart relocations. Also images of corresponding
|
||||||
|
kickstart ROM images will be needed.
|
||||||
|
|
||||||
|
|
||||||
|
and then ``s/startup-sequence`` might looks a follows:
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
Assign >NIL: ENV: RAM:
|
||||||
|
Assign >NIL: T: RAM:
|
||||||
|
|
||||||
|
setpatch QUIET
|
||||||
|
|
||||||
|
IF EXISTS S:whdload-startup
|
||||||
|
Execute S:whdload-startup
|
||||||
|
EndIF
|
||||||
|
|
||||||
|
C:UAEquit
|
||||||
|
|
||||||
|
Creating base image archive
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Now, the tree for the minimal image could look like that:
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
.
|
||||||
|
├── C
|
||||||
|
│  ├── Assign
|
||||||
|
│  ├── DIC
|
||||||
|
│  ├── Execute
|
||||||
|
│  ├── kgiconload
|
||||||
|
│  ├── Patcher
|
||||||
|
│  ├── RawDIC
|
||||||
|
│  ├── SetPatch
|
||||||
|
│  ├── UAEquit
|
||||||
|
│  ├── WHDLoad
|
||||||
|
│  └── WHDLoadCD32
|
||||||
|
└── S
|
||||||
|
├── startup-sequence
|
||||||
|
└── WHDLoad.prefs
|
||||||
|
|
||||||
|
to use relocation tables you'll need to place ``Kickstarts`` drawer into Devs
|
||||||
|
drawer. Also keep in mind, that corresponding kickstart rom images need to be
|
||||||
|
placed there as well, otherwise it may or may not work. Structure looks like
|
||||||
|
this:
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
.
|
||||||
|
├── C
|
||||||
|
│  ├── Assign
|
||||||
|
│  ├── …
|
||||||
|
│  └── WHDLoadCD32
|
||||||
|
├── Devs
|
||||||
|
│  └── Kickstarts
|
||||||
|
│  ├── 39115_ROMKick.PAT
|
||||||
|
│  ├── 39115_ROMKick.RTB
|
||||||
|
│  ├── …
|
||||||
|
│  ├── kick40068.A4000.PAT
|
||||||
|
│  └── kick40068.A4000.RTB
|
||||||
|
└── S
|
||||||
|
├── startup-sequence
|
||||||
|
└── WHDLoad.prefs
|
||||||
|
|
||||||
|
Important: You'll need to prepare archive with base OS without top directory,
|
||||||
|
i.e. suppose you have prepared all the files in ``/tmp/baseos``:
|
||||||
|
|
||||||
|
.. code:: shell-session
|
||||||
|
|
||||||
|
$ pwd
|
||||||
|
/tmp
|
||||||
|
$ cd baseos
|
||||||
|
$ pwd
|
||||||
|
/tmp/basos
|
||||||
|
$ ls
|
||||||
|
C S
|
||||||
|
$ zip -r /tmp/base.zip .
|
||||||
|
adding: C/ (stored 0%)
|
||||||
|
adding: C/Assign (deflated 31%)
|
||||||
|
adding: C/WHDLoadCD32 (deflated 26%)
|
||||||
|
adding: C/RawDIC (deflated 46%)
|
||||||
|
adding: C/UAEquit (deflated 39%)
|
||||||
|
adding: C/Execute (deflated 42%)
|
||||||
|
adding: C/Patcher (deflated 56%)
|
||||||
|
adding: C/DIC (deflated 33%)
|
||||||
|
adding: C/kgiconload (deflated 49%)
|
||||||
|
adding: C/SetPatch (deflated 39%)
|
||||||
|
adding: C/WHDLoad (deflated 23%)
|
||||||
|
adding: S/ (stored 0%)
|
||||||
|
adding: S/startup-sequence (deflated 36%)
|
||||||
|
adding: S/WHDLoad.prefs (deflated 51%)
|
||||||
|
|
||||||
|
You can do it with other archivers as well, like 7z: ``7z a /tmp/base.7z .`` or
|
||||||
|
tar with different compressions: ``tar Jcf /tmp/base.tar.xz .``, ``tar zcf
|
||||||
|
/tmp/base.tgz .``, ``tar jcf /tmp/base.tar.bz2 .``. It should work with all
|
||||||
|
mentioned at the beginning of this document archivers.
|
||||||
|
|
||||||
|
Starting point is in ``S/startup-sequence`` file, where eventually
|
||||||
|
``S/whdload-startup`` is executed, which will be created by fs-uae-warpper
|
||||||
|
before execution by fs-uae.
|
||||||
|
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Now, to use whdload module with any of the WHDLoad game, you'll need to prepare
|
||||||
|
configuration for the wrapper.
|
||||||
|
|
||||||
|
Example configuration:
|
||||||
|
|
||||||
|
.. code:: ini
|
||||||
|
|
||||||
|
[config]
|
||||||
|
wrapper = whdload
|
||||||
|
wrapper_whdload_base = $CONFIG/whdload_base.7z
|
||||||
|
# ...
|
||||||
|
|
||||||
|
And execution is as usual:
|
||||||
|
|
||||||
|
.. code:: shell-session
|
||||||
|
|
||||||
|
$ fs-uae-wrapper ChaosEngine_v1.2_0106.fs-uae
|
||||||
|
|
||||||
|
Now, similar to the archive module, it will create temporary directory, unpack
|
||||||
|
base image there, unpack WHDLoad game archive, search for slave file, and
|
||||||
|
prepare ``s:whdload-startup``, and finally pass all the configuration to
|
||||||
|
fs-uae.
|
||||||
|
|
||||||
|
|
||||||
|
Limitations
|
||||||
|
===========
|
||||||
|
|
||||||
|
There is one limitation when using save ``wrapper_save_state`` option. In case
|
||||||
|
of floppies it should work without any issues, although save state for running
|
||||||
|
Workbench or WHDLoad games may or may not work. In the past there was an issue
|
||||||
|
with `fs-uae`_ where saving state was causing data corruption on the emulated
|
||||||
|
system. Use it with caution!
|
||||||
|
|
||||||
|
|
||||||
License
|
License
|
||||||
=======
|
=======
|
||||||
|
|
||||||
@@ -325,3 +535,8 @@ This work is licensed on 3-clause BSD license. See LICENSE file for details.
|
|||||||
.. _tar: https://www.gnu.org/software/tar/
|
.. _tar: https://www.gnu.org/software/tar/
|
||||||
.. _zip: http://www.info-zip.org
|
.. _zip: http://www.info-zip.org
|
||||||
.. _CheeseShop: https://pypi.python.org/pypi/fs-/fs-uae-wrapperuae-wrapper
|
.. _CheeseShop: https://pypi.python.org/pypi/fs-/fs-uae-wrapperuae-wrapper
|
||||||
|
.. _WHDLoad: https://www.whdload.de
|
||||||
|
.. _uaequit: https://aminet.net/package/misc/emu/UAEquit
|
||||||
|
.. _SKick: https://aminet.net/package/util/boot/skick346
|
||||||
|
.. _SetPatch: https://aminet.net/package/util/boot/SetPatch_43.6b
|
||||||
|
.. _kgiconload: https://eab.abime.net/showpost.php?p=733614&postcount=92
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ the temporary one.
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from fs_uae_wrapper import base
|
from fs_uae_wrapper import base, utils
|
||||||
from fs_uae_wrapper import utils
|
|
||||||
|
|
||||||
|
|
||||||
class Wrapper(base.ArchiveBase):
|
class Wrapper(base.ArchiveBase):
|
||||||
|
|||||||
@@ -4,11 +4,9 @@ Base class for all wrapper modules
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from fs_uae_wrapper import utils
|
from fs_uae_wrapper import path, utils
|
||||||
from fs_uae_wrapper import path
|
|
||||||
|
|
||||||
|
|
||||||
class Base(object):
|
class Base(object):
|
||||||
@@ -42,10 +40,11 @@ class Base(object):
|
|||||||
- run the emulation
|
- run the emulation
|
||||||
- archive save state
|
- archive save state
|
||||||
"""
|
"""
|
||||||
|
logging.debug("run")
|
||||||
if not self._validate_options():
|
if not self._validate_options():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.dir = tempfile.mkdtemp()
|
self.dir = tempfile.mkdtemp(prefix='fs-uae-wrapper-')
|
||||||
self._normalize_options()
|
self._normalize_options()
|
||||||
self._set_assets_paths()
|
self._set_assets_paths()
|
||||||
|
|
||||||
@@ -83,7 +82,7 @@ class Base(object):
|
|||||||
"""execute fs-uae"""
|
"""execute fs-uae"""
|
||||||
curdir = os.path.abspath('.')
|
curdir = os.path.abspath('.')
|
||||||
os.chdir(self.dir)
|
os.chdir(self.dir)
|
||||||
utils.run_command(['fs-uae'] + self.fsuae_options.list())
|
utils.run_command(['fs-uae', *self.fsuae_options.list()])
|
||||||
os.chdir(curdir)
|
os.chdir(curdir)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -176,13 +175,14 @@ class Base(object):
|
|||||||
Configuration file will be placed in new directory, therefore it is
|
Configuration file will be placed in new directory, therefore it is
|
||||||
needed to calculate new paths so that emulator can find assets.
|
needed to calculate new paths so that emulator can find assets.
|
||||||
"""
|
"""
|
||||||
options = ['wrapper_archive', 'accelerator_rom', 'base_dir',
|
logging.debug("_normalize_options")
|
||||||
'cdrom_drive_0', 'cdroms_dir', 'controllers_dir',
|
options = ['wrapper_archive', 'wrapper_whdload_base',
|
||||||
'cpuboard_flash_ext_file', 'cpuboard_flash_file',
|
'accelerator_rom', 'base_dir', 'cdrom_drive_0',
|
||||||
'floppies_dir', 'floppy_overlays_dir', 'fmv_rom',
|
'cdroms_dir', 'controllers_dir', 'cpuboard_flash_ext_file',
|
||||||
'graphics_card_rom', 'hard_drives_dir', 'kickstart_file',
|
'cpuboard_flash_file', 'floppies_dir',
|
||||||
'kickstarts_dir', 'logs_dir', 'save_states_dir',
|
'floppy_overlays_dir', 'fmv_rom', 'graphics_card_rom',
|
||||||
'screenshots_output_dir']
|
'hard_drives_dir', 'kickstart_file', 'kickstarts_dir',
|
||||||
|
'logs_dir', 'save_states_dir', 'screenshots_output_dir']
|
||||||
|
|
||||||
for num in range(20):
|
for num in range(20):
|
||||||
options.append('cdrom_image_%d' % num)
|
options.append('cdrom_image_%d' % num)
|
||||||
@@ -217,6 +217,7 @@ class Base(object):
|
|||||||
if val.startswith('$CONFIG'):
|
if val.startswith('$CONFIG'):
|
||||||
abspath = utils.interpolate_variables(val, self.conf_file)
|
abspath = utils.interpolate_variables(val, self.conf_file)
|
||||||
changed_options[key] = abspath
|
changed_options[key] = abspath
|
||||||
|
logging.info("%s: %s => %s", key, val, abspath)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
_val = os.path.abspath(val)
|
_val = os.path.abspath(val)
|
||||||
@@ -237,9 +238,9 @@ class Base(object):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
if 'wrapper_archiver' not in self.all_options:
|
if 'wrapper_archiver' not in self.all_options:
|
||||||
logging.error("Configuration lacks of required "
|
logging.warning("Configuration lacks of optional "
|
||||||
"`wrapper_archiver' option.")
|
"`wrapper_archiver' option, fall back to 7z")
|
||||||
return False
|
self.all_options['wrapper_archiver'] = "7z"
|
||||||
|
|
||||||
if not path.which(self.all_options['wrapper_archiver']):
|
if not path.which(self.all_options['wrapper_archiver']):
|
||||||
logging.error("Cannot find archiver `%s'.",
|
logging.error("Cannot find archiver `%s'.",
|
||||||
@@ -282,6 +283,7 @@ class ArchiveBase(Base):
|
|||||||
|
|
||||||
def _extract(self):
|
def _extract(self):
|
||||||
"""Extract archive to temp dir"""
|
"""Extract archive to temp dir"""
|
||||||
|
logging.debug("_extract")
|
||||||
|
|
||||||
title = self._get_title()
|
title = self._get_title()
|
||||||
curdir = os.path.abspath('.')
|
curdir = os.path.abspath('.')
|
||||||
@@ -291,12 +293,36 @@ class ArchiveBase(Base):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def _validate_options(self):
|
def _validate_options(self):
|
||||||
|
logging.debug("_validate_options")
|
||||||
|
|
||||||
validation_result = super(ArchiveBase, self)._validate_options()
|
validation_result = super(ArchiveBase, self)._validate_options()
|
||||||
|
if not validation_result:
|
||||||
|
return False
|
||||||
|
|
||||||
if 'wrapper_archive' not in self.all_options:
|
if 'wrapper_archive' not in self.all_options:
|
||||||
sys.stderr.write("Configuration lacks of required "
|
logging.warning("Configuration lacks of optional `wrapper_archive'"
|
||||||
"`wrapper_archive' option.\n")
|
" option.\n")
|
||||||
|
wrapper_archive = self._get_wrapper_archive_name()
|
||||||
|
if wrapper_archive is None:
|
||||||
|
logging.error("Configuration lacks of optional "
|
||||||
|
"`wrapper_archive', cannot deduct the name by "
|
||||||
|
"configuration file name.\n")
|
||||||
validation_result = False
|
validation_result = False
|
||||||
|
self.all_options['wrapper_archive'] = wrapper_archive
|
||||||
|
|
||||||
return validation_result
|
return validation_result
|
||||||
|
|
||||||
|
def _get_wrapper_archive_name(self):
|
||||||
|
"""
|
||||||
|
Return full path to the archive name using configuration file
|
||||||
|
basename and appending one of the expected archive extensions.
|
||||||
|
"""
|
||||||
|
basename = os.path.splitext(os.path.basename(self.conf_file))[0]
|
||||||
|
file_list = os.listdir(os.path.dirname(self.conf_file))
|
||||||
|
for fname in file_list:
|
||||||
|
for ext in ('.7z', '.lha', '.lzx', '.zip', '.rar', '.tar', '.tgz',
|
||||||
|
'.tar.gz', '.tar.bz2', '.tar.xz'):
|
||||||
|
if ((basename + ext).lower() == fname.lower() and
|
||||||
|
basename == os.path.splitext(fname)[0]):
|
||||||
|
return fname
|
||||||
|
return None
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
"""
|
"""
|
||||||
File archive classes
|
File archive classes
|
||||||
"""
|
"""
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import re
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
|
||||||
from fs_uae_wrapper import path
|
from fs_uae_wrapper import path
|
||||||
|
|
||||||
|
|
||||||
class Archive(object):
|
class Archive(object):
|
||||||
"""Base class for archive support"""
|
"""Base class for archive support"""
|
||||||
ADD = ['a']
|
ADD = ('a',)
|
||||||
EXTRACT = ['x']
|
EXTRACT = ('x',)
|
||||||
ARCH = 'false'
|
ARCH = 'false'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -27,8 +27,8 @@ class Archive(object):
|
|||||||
files = files if files else ['.']
|
files = files if files else ['.']
|
||||||
logging.debug("Calling `%s %s %s %s'.", self._compress,
|
logging.debug("Calling `%s %s %s %s'.", self._compress,
|
||||||
" ".join(self.ADD), arch_name, " ".join(files))
|
" ".join(self.ADD), arch_name, " ".join(files))
|
||||||
result = subprocess.call([self._compress] + self.ADD + [arch_name]
|
result = subprocess.call([self._compress, *self.ADD, arch_name,
|
||||||
+ files)
|
*files])
|
||||||
if result != 0:
|
if result != 0:
|
||||||
logging.error("Unable to create archive `%s'.", arch_name)
|
logging.error("Unable to create archive `%s'.", arch_name)
|
||||||
return False
|
return False
|
||||||
@@ -44,8 +44,7 @@ class Archive(object):
|
|||||||
|
|
||||||
logging.debug("Calling `%s %s %s'.", self._compress,
|
logging.debug("Calling `%s %s %s'.", self._compress,
|
||||||
" ".join(self.ADD), arch_name)
|
" ".join(self.ADD), arch_name)
|
||||||
result = subprocess.call([self._decompress] + self.EXTRACT +
|
result = subprocess.call([self._decompress, *self.EXTRACT, arch_name])
|
||||||
[arch_name])
|
|
||||||
if result != 0:
|
if result != 0:
|
||||||
logging.error("Unable to extract archive `%s'.", arch_name)
|
logging.error("Unable to extract archive `%s'.", arch_name)
|
||||||
return False
|
return False
|
||||||
@@ -53,16 +52,16 @@ class Archive(object):
|
|||||||
|
|
||||||
|
|
||||||
class TarArchive(Archive):
|
class TarArchive(Archive):
|
||||||
ADD = ['cf']
|
ADD = ('cf',)
|
||||||
EXTRACT = ['xf']
|
EXTRACT = ('xf',)
|
||||||
ARCH = 'tar'
|
ARCH = 'tar'
|
||||||
|
|
||||||
def create(self, arch_name, files=None):
|
def create(self, arch_name, files=None):
|
||||||
files = files if files else sorted(os.listdir('.'))
|
files = files if files else sorted(os.listdir('.'))
|
||||||
logging.debug("Calling `%s %s %s %s'.", self._compress,
|
logging.debug("Calling `%s %s %s %s'.", self._compress,
|
||||||
" ".join(self.ADD), arch_name, " ".join(files))
|
" ".join(self.ADD), arch_name, " ".join(files))
|
||||||
result = subprocess.call([self._compress] + self.ADD + [arch_name] +
|
result = subprocess.call([self._compress, *self.ADD, arch_name,
|
||||||
files)
|
*files])
|
||||||
if result != 0:
|
if result != 0:
|
||||||
logging.error("Unable to create archive `%s'.", arch_name)
|
logging.error("Unable to create archive `%s'.", arch_name)
|
||||||
return False
|
return False
|
||||||
@@ -70,15 +69,15 @@ class TarArchive(Archive):
|
|||||||
|
|
||||||
|
|
||||||
class TarGzipArchive(TarArchive):
|
class TarGzipArchive(TarArchive):
|
||||||
ADD = ['zcf']
|
ADD = ('zcf',)
|
||||||
|
|
||||||
|
|
||||||
class TarBzip2Archive(TarArchive):
|
class TarBzip2Archive(TarArchive):
|
||||||
ADD = ['jcf']
|
ADD = ('jcf',)
|
||||||
|
|
||||||
|
|
||||||
class TarXzArchive(TarArchive):
|
class TarXzArchive(TarArchive):
|
||||||
ADD = ['Jcf']
|
ADD = ('Jcf',)
|
||||||
|
|
||||||
|
|
||||||
class LhaArchive(Archive):
|
class LhaArchive(Archive):
|
||||||
@@ -86,8 +85,8 @@ class LhaArchive(Archive):
|
|||||||
|
|
||||||
|
|
||||||
class ZipArchive(Archive):
|
class ZipArchive(Archive):
|
||||||
ADD = ['a', '-tzip']
|
ADD = ('a', '-tzip')
|
||||||
ARCH = ['7z', 'zip']
|
ARCH = ('7z', 'zip')
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(ZipArchive, self).__init__()
|
super(ZipArchive, self).__init__()
|
||||||
@@ -102,7 +101,7 @@ class SevenZArchive(Archive):
|
|||||||
|
|
||||||
|
|
||||||
class LzxArchive(Archive):
|
class LzxArchive(Archive):
|
||||||
EXTRACT = ['-x']
|
EXTRACT = ('-x',)
|
||||||
ARCH = 'unlzx'
|
ARCH = 'unlzx'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -113,7 +112,7 @@ class LzxArchive(Archive):
|
|||||||
|
|
||||||
|
|
||||||
class RarArchive(Archive):
|
class RarArchive(Archive):
|
||||||
ARCH = ['rar', 'unrar']
|
ARCH = ('rar', 'unrar')
|
||||||
|
|
||||||
def create(self, arch_name, files=None):
|
def create(self, arch_name, files=None):
|
||||||
files = files if files else sorted(os.listdir('.'))
|
files = files if files else sorted(os.listdir('.'))
|
||||||
@@ -124,8 +123,8 @@ class RarArchive(Archive):
|
|||||||
|
|
||||||
logging.debug("Calling `%s %s %s %s'.", self._compress,
|
logging.debug("Calling `%s %s %s %s'.", self._compress,
|
||||||
" ".join(self.ADD), arch_name, " ".join(files))
|
" ".join(self.ADD), arch_name, " ".join(files))
|
||||||
result = subprocess.call([self._compress] + self.ADD + [arch_name] +
|
result = subprocess.call([self._compress, *self.ADD, arch_name,
|
||||||
files)
|
*files])
|
||||||
if result != 0:
|
if result != 0:
|
||||||
logging.error("Unable to create archive `%s'.", arch_name)
|
logging.error("Unable to create archive `%s'.", arch_name)
|
||||||
return False
|
return False
|
||||||
@@ -134,7 +133,7 @@ class RarArchive(Archive):
|
|||||||
|
|
||||||
class Archivers(object):
|
class Archivers(object):
|
||||||
"""Archivers class"""
|
"""Archivers class"""
|
||||||
archivers = [{'arch': TarArchive, 'name': 'tar', 'ext': ['tar']},
|
archivers = ({'arch': TarArchive, 'name': 'tar', 'ext': ['tar']},
|
||||||
{'arch': TarGzipArchive, 'name': 'tgz',
|
{'arch': TarGzipArchive, 'name': 'tgz',
|
||||||
'ext': ['tar.gz', 'tgz']},
|
'ext': ['tar.gz', 'tgz']},
|
||||||
{'arch': TarBzip2Archive, 'name': 'tar.bz2',
|
{'arch': TarBzip2Archive, 'name': 'tar.bz2',
|
||||||
@@ -144,7 +143,7 @@ class Archivers(object):
|
|||||||
{'arch': SevenZArchive, 'name': '7z', 'ext': ['7z']},
|
{'arch': SevenZArchive, 'name': '7z', 'ext': ['7z']},
|
||||||
{'arch': ZipArchive, 'name': 'zip', 'ext': ['zip']},
|
{'arch': ZipArchive, 'name': 'zip', 'ext': ['zip']},
|
||||||
{'arch': LhaArchive, 'name': 'lha', 'ext': ['lha', 'lzh']},
|
{'arch': LhaArchive, 'name': 'lha', 'ext': ['lha', 'lzh']},
|
||||||
{'arch': LzxArchive, 'name': 'lzx', 'ext': ['lzx']}]
|
{'arch': LzxArchive, 'name': 'lzx', 'ext': ['lzx']})
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls, extension):
|
def get(cls, extension):
|
||||||
|
|||||||
@@ -3,19 +3,15 @@ Display message in separate process
|
|||||||
"""
|
"""
|
||||||
import multiprocessing as mp
|
import multiprocessing as mp
|
||||||
import sys
|
import sys
|
||||||
try:
|
import tkinter
|
||||||
import tkinter as tk
|
import tkinter.ttk
|
||||||
from tkinter import ttk
|
|
||||||
except ImportError:
|
|
||||||
import Tkinter as tk
|
|
||||||
import ttk
|
|
||||||
|
|
||||||
|
|
||||||
class MessageGui(tk.Tk):
|
class MessageGui(tkinter.Tk):
|
||||||
"""Simple gui for displaying a message"""
|
"""Simple gui for displaying a message"""
|
||||||
|
|
||||||
def __init__(self, parent=None, msg=""):
|
def __init__(self, parent=None, msg=""):
|
||||||
tk.Tk.__init__(self, parent)
|
tkinter.Tk.__init__(self, parent)
|
||||||
|
|
||||||
self.grid()
|
self.grid()
|
||||||
self.resizable(False, False)
|
self.resizable(False, False)
|
||||||
@@ -24,12 +20,13 @@ class MessageGui(tk.Tk):
|
|||||||
# Display window without decorations
|
# Display window without decorations
|
||||||
self.wm_attributes('-type', 'splash')
|
self.wm_attributes('-type', 'splash')
|
||||||
|
|
||||||
self.frame = ttk.Frame(self, padding=5, borderwidth=0)
|
self.frame = tkinter.ttk.Frame(self, padding=5, borderwidth=0)
|
||||||
self.frame.grid()
|
self.frame.grid()
|
||||||
ttk.Label(self.frame, text=msg, relief="ridge", padding=10).grid()
|
tkinter.ttk.Label(self.frame, text=msg, relief="ridge",
|
||||||
|
padding=10).grid()
|
||||||
|
|
||||||
if 'linux' in sys.platform:
|
if 'linux' in sys.platform:
|
||||||
style = ttk.Style()
|
style = tkinter.ttk.Style()
|
||||||
style.theme_use('clam')
|
style.theme_use('clam')
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
|
|||||||
18
fs_uae_wrapper/nogui_message.py
Normal file
18
fs_uae_wrapper/nogui_message.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
"""
|
||||||
|
Display message as simple text on console
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
class Message:
|
||||||
|
"""Just a fake message window for systems without TK"""
|
||||||
|
|
||||||
|
def __init__(self, msg):
|
||||||
|
self.msg = msg
|
||||||
|
self._process = None
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
sys.stdout.write(self.msg + "\n")
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
return None
|
||||||
@@ -2,8 +2,7 @@
|
|||||||
Simple class for executing fs-uae with specified parameters. This is a
|
Simple class for executing fs-uae with specified parameters. This is a
|
||||||
failsafe class for running fs-uae.
|
failsafe class for running fs-uae.
|
||||||
"""
|
"""
|
||||||
from fs_uae_wrapper import base
|
from fs_uae_wrapper import base, utils
|
||||||
from fs_uae_wrapper import utils
|
|
||||||
|
|
||||||
|
|
||||||
class Wrapper(base.Base):
|
class Wrapper(base.Base):
|
||||||
@@ -17,8 +16,8 @@ class Wrapper(base.Base):
|
|||||||
|
|
||||||
def _run_emulator(self):
|
def _run_emulator(self):
|
||||||
"""execute fs-uae"""
|
"""execute fs-uae"""
|
||||||
utils.run_command(['fs-uae'] + [self.conf_file] +
|
utils.run_command(['fs-uae', self.conf_file,
|
||||||
self.fsuae_options.list())
|
*self.fsuae_options.list()])
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
"""Do the cleanup. Here - just do nothing"""
|
"""Do the cleanup. Here - just do nothing"""
|
||||||
|
|||||||
@@ -4,11 +4,15 @@ Misc utilities
|
|||||||
import configparser
|
import configparser
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import pathlib
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from fs_uae_wrapper import message
|
|
||||||
from fs_uae_wrapper import file_archive
|
from fs_uae_wrapper import file_archive
|
||||||
|
try:
|
||||||
|
from fs_uae_wrapper.message import Message
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
from fs_uae_wrapper.nogui_message import Message
|
||||||
|
|
||||||
|
|
||||||
class CmdOption(dict):
|
class CmdOption(dict):
|
||||||
@@ -19,8 +23,8 @@ class CmdOption(dict):
|
|||||||
def add(self, option):
|
def add(self, option):
|
||||||
"""parse and add option to the dictionary"""
|
"""parse and add option to the dictionary"""
|
||||||
if not option.startswith('--'):
|
if not option.startswith('--'):
|
||||||
raise AttributeError("Cannot add option `%s' to the dictionary" %
|
raise AttributeError(f"Cannot add option {option} to the "
|
||||||
option)
|
f"dictionary")
|
||||||
if '=' in option:
|
if '=' in option:
|
||||||
key, val = option.split('=', 1)
|
key, val = option.split('=', 1)
|
||||||
key = key[2:].strip()
|
key = key[2:].strip()
|
||||||
@@ -35,9 +39,9 @@ class CmdOption(dict):
|
|||||||
ret_list = []
|
ret_list = []
|
||||||
for key, val in self.items():
|
for key, val in self.items():
|
||||||
if val != '1':
|
if val != '1':
|
||||||
ret_list.append('--%(k)s=%(v)s' % {'k': key, 'v': val})
|
ret_list.append(f'--{key}={val}')
|
||||||
else:
|
else:
|
||||||
ret_list.append('--%(k)s' % {'k': key})
|
ret_list.append(f'--{key}')
|
||||||
return ret_list
|
return ret_list
|
||||||
|
|
||||||
|
|
||||||
@@ -64,7 +68,7 @@ def operate_archive(arch_name, operation, text, params):
|
|||||||
if archiver is None:
|
if archiver is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
msg = message.Message(text)
|
msg = Message(text)
|
||||||
if text:
|
if text:
|
||||||
msg.show()
|
msg.show()
|
||||||
|
|
||||||
@@ -87,7 +91,7 @@ def create_archive(arch_name, title='', params=None):
|
|||||||
"""
|
"""
|
||||||
msg = ''
|
msg = ''
|
||||||
if title:
|
if title:
|
||||||
msg = "Creating archive for `%s'. Please be patient" % title
|
msg = f"Creating archive for `{title}'. Please be patient"
|
||||||
return operate_archive(arch_name, 'create', msg, params)
|
return operate_archive(arch_name, 'create', msg, params)
|
||||||
|
|
||||||
|
|
||||||
@@ -97,7 +101,7 @@ def extract_archive(arch_name, title='', params=None):
|
|||||||
"""
|
"""
|
||||||
msg = ''
|
msg = ''
|
||||||
if title:
|
if title:
|
||||||
msg = "Extracting files for `%s'. Please be patient" % title
|
msg = f"Extracting files for `{title}'. Please be patient"
|
||||||
return operate_archive(arch_name, 'extract', msg, params)
|
return operate_archive(arch_name, 'extract', msg, params)
|
||||||
|
|
||||||
|
|
||||||
@@ -143,8 +147,9 @@ def interpolate_variables(string, config_path, base=None):
|
|||||||
|
|
||||||
_string = string
|
_string = string
|
||||||
if '$CONFIG' in string:
|
if '$CONFIG' in string:
|
||||||
conf_path = os.path.dirname(os.path.abspath(config_path))
|
conf_path = pathlib.Path(config_path).resolve().parent
|
||||||
string = os.path.abspath(string.replace('$CONFIG', conf_path))
|
string = str(pathlib.Path(string.replace('$CONFIG', str(conf_path)))
|
||||||
|
.resolve())
|
||||||
|
|
||||||
if '$HOME' in string:
|
if '$HOME' in string:
|
||||||
string = string.replace('$HOME', os.path.expandvars('$HOME'))
|
string = string.replace('$HOME', os.path.expandvars('$HOME'))
|
||||||
|
|||||||
136
fs_uae_wrapper/whdload.py
Normal file
136
fs_uae_wrapper/whdload.py
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
"""
|
||||||
|
Run fs-uae with WHDLoad games
|
||||||
|
|
||||||
|
It will use compressed base image and compressed directories.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
from fs_uae_wrapper import base, utils
|
||||||
|
|
||||||
|
|
||||||
|
class Wrapper(base.ArchiveBase):
|
||||||
|
"""
|
||||||
|
Class for performing extracting archive, copying emulator files, and
|
||||||
|
cleaning it back again
|
||||||
|
"""
|
||||||
|
def __init__(self, conf_file, fsuae_options, configuration):
|
||||||
|
super(Wrapper, self).__init__(conf_file, fsuae_options, configuration)
|
||||||
|
self.archive_type = None
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""
|
||||||
|
Main function which accepts configuration file for FS-UAE
|
||||||
|
It will do as follows:
|
||||||
|
- extract base image and archive file
|
||||||
|
- copy configuration
|
||||||
|
- run the emulation
|
||||||
|
"""
|
||||||
|
logging.debug("run")
|
||||||
|
if not super().run():
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not self._extract():
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not self._copy_conf():
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self._run_emulator()
|
||||||
|
|
||||||
|
def _validate_options(self):
|
||||||
|
"""
|
||||||
|
Do the validation for the options, additionally check if there is
|
||||||
|
mandatory WHDLoad base OS images set.
|
||||||
|
"""
|
||||||
|
if not super()._validate_options():
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not self.all_options.get('wrapper_whdload_base'):
|
||||||
|
logging.error("wrapper_whdload_base is not set in configuration, "
|
||||||
|
"exiting.")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _extract(self):
|
||||||
|
"""Extract base image and then WHDLoad archive"""
|
||||||
|
base_image = self.fsuae_options['wrapper_whdload_base']
|
||||||
|
if not os.path.exists(base_image):
|
||||||
|
logging.error("Base image `%s` does't exists in provided "
|
||||||
|
"location.", base_image)
|
||||||
|
return False
|
||||||
|
|
||||||
|
curdir = os.path.abspath('.')
|
||||||
|
os.chdir(self.dir)
|
||||||
|
result = utils.extract_archive(base_image)
|
||||||
|
os.chdir(curdir)
|
||||||
|
if not result:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not super()._extract():
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self._find_slave()
|
||||||
|
|
||||||
|
def _find_slave(self):
|
||||||
|
"""Find Slave file and create apropriate entry in S:whdload-startup"""
|
||||||
|
curdir = os.path.abspath('.')
|
||||||
|
os.chdir(self.dir)
|
||||||
|
|
||||||
|
# find slave name
|
||||||
|
slave_fname = None
|
||||||
|
slave_path = None
|
||||||
|
case_insensitvie_map = {}
|
||||||
|
|
||||||
|
# build case insensitive map of paths and find the slave file
|
||||||
|
for root, dirnames, fnames in os.walk('.'):
|
||||||
|
for dirname in dirnames:
|
||||||
|
full_path = os.path.normpath(os.path.join(root, dirname))
|
||||||
|
case_insensitvie_map[full_path.lower()] = full_path
|
||||||
|
|
||||||
|
for fname in fnames:
|
||||||
|
full_path = os.path.normpath(os.path.join(root, fname))
|
||||||
|
case_insensitvie_map[full_path.lower()] = full_path
|
||||||
|
if not slave_fname and fname.lower().endswith('.slave'):
|
||||||
|
slave_path, slave_fname = os.path.normpath(root), fname
|
||||||
|
|
||||||
|
if slave_fname is None:
|
||||||
|
logging.error("Cannot find .slave file in archive.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# find corresponfing info (an icon) fname
|
||||||
|
icon_fname = None
|
||||||
|
for fname in os.listdir(slave_path):
|
||||||
|
if (fname.lower().endswith('.info') and
|
||||||
|
os.path.splitext(slave_fname)[0].lower() ==
|
||||||
|
os.path.splitext(fname)[0].lower()):
|
||||||
|
icon_fname = fname
|
||||||
|
break
|
||||||
|
if icon_fname is None:
|
||||||
|
logging.error("Cannot find .info file corresponding to %s in "
|
||||||
|
"archive.", slave_fname)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# find proper way to handle slave
|
||||||
|
# 1. check if there are user provided params
|
||||||
|
contents = f"cd {slave_path}\n"
|
||||||
|
if self.all_options.get('wrapper_whdload_options'):
|
||||||
|
contents = (f"{contents}"
|
||||||
|
f"C:whdload "
|
||||||
|
f"{self.all_options['wrapper_whdload_options']} "
|
||||||
|
f"Slave={slave_fname}\n")
|
||||||
|
else:
|
||||||
|
# no params, find if kgiconload is available
|
||||||
|
if case_insensitvie_map.get('c/kgiconload'):
|
||||||
|
contents = f"{contents}C:kgiconload {icon_fname}\n"
|
||||||
|
else:
|
||||||
|
# if not, just add common defaults
|
||||||
|
contents = (f"{contents}C:whdload Preload "
|
||||||
|
f"Slave={slave_fname}\n")
|
||||||
|
|
||||||
|
fname = os.path.join(case_insensitvie_map.get('s'), 'whdload-startup')
|
||||||
|
with open(fname, "w") as fobj:
|
||||||
|
fobj.write(contents)
|
||||||
|
|
||||||
|
os.chdir(curdir)
|
||||||
|
return True
|
||||||
@@ -7,8 +7,7 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from fs_uae_wrapper import utils
|
from fs_uae_wrapper import WRAPPER_KEY, utils
|
||||||
from fs_uae_wrapper import WRAPPER_KEY
|
|
||||||
|
|
||||||
|
|
||||||
def setup_logger(options):
|
def setup_logger(options):
|
||||||
@@ -27,7 +26,8 @@ def setup_logger(options):
|
|||||||
level = logging.DEBUG
|
level = logging.DEBUG
|
||||||
|
|
||||||
logging.basicConfig(level=level,
|
logging.basicConfig(level=level,
|
||||||
format="%(asctime)s %(levelname)s: %(message)s")
|
format="%(asctime)s %(levelname)s\t%(filename)s:"
|
||||||
|
"%(lineno)d:\t\t%(message)s")
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
|
|||||||
50
pyproject.toml
Normal file
50
pyproject.toml
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools >= 77.0", "wheel", "setuptools-git-versioning"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "fs-uae-wrapper"
|
||||||
|
authors = [{name = "Roman Dobosz", email = "gryf73@gmail.com"}]
|
||||||
|
license = "BSD-3-Clause"
|
||||||
|
description = "Automate archives support and state saves for fs-uae"
|
||||||
|
readme = "README.rst"
|
||||||
|
requires-python = ">=3.8"
|
||||||
|
keywords = ["uae", "fs-uae", "amiga", "emulator", "wrapper"]
|
||||||
|
dynamic = ["version"]
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 5 - Production/Stable",
|
||||||
|
"Environment :: Console",
|
||||||
|
"Intended Audience :: End Users/Desktop",
|
||||||
|
"Operating System :: OS Independent",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.8",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
|
"Topic :: System :: Emulators",
|
||||||
|
"Topic :: Games/Entertainment"
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://github.com/gryf/fs-uae-wrapper"
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
fs-uae-wrapper = "fs_uae_wrapper.wrapper:run"
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
packages = ["fs_uae_wrapper"]
|
||||||
|
|
||||||
|
[tool.distutils.bdist_wheel]
|
||||||
|
universal = true
|
||||||
|
|
||||||
|
[tool.setuptools-git-versioning]
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = [
|
||||||
|
"F", # pyflakes
|
||||||
|
"E", # pycodestyle
|
||||||
|
"I", # isort
|
||||||
|
"RUF", # ruff-specific rules
|
||||||
|
]
|
||||||
36
setup.cfg
36
setup.cfg
@@ -1,36 +0,0 @@
|
|||||||
[metadata]
|
|
||||||
name = fs-uae-wrapper
|
|
||||||
summary = Automate archives and state for fs-uae
|
|
||||||
description_file = README.rst
|
|
||||||
author = Roman Dobosz
|
|
||||||
author_email = gryf73@gmail.com
|
|
||||||
url = https://github.com/gryf/fs-uea-wrapper
|
|
||||||
license = BSD
|
|
||||||
keywords = uae, fs-uae, amiga, emulator, wrapper
|
|
||||||
version = 0.8.2
|
|
||||||
classifier =
|
|
||||||
Development Status :: 5 - Production/Stable
|
|
||||||
Environment :: Console
|
|
||||||
Intended Audience :: End Users/Desktop
|
|
||||||
License :: OSI Approved :: BSD License
|
|
||||||
Operating System :: OS Independent
|
|
||||||
Programming Language :: Python :: 3
|
|
||||||
Programming Language :: Python :: 3.8
|
|
||||||
Programming Language :: Python :: 3.9
|
|
||||||
Programming Language :: Python :: 3.10
|
|
||||||
Topic :: System :: Emulators
|
|
||||||
Topic :: Games/Entertainment
|
|
||||||
|
|
||||||
[install]
|
|
||||||
record = install.log
|
|
||||||
|
|
||||||
[options.entry_points]
|
|
||||||
console_scripts =
|
|
||||||
fs-uae-wrapper = fs_uae_wrapper.wrapper:run
|
|
||||||
|
|
||||||
[files]
|
|
||||||
packages =
|
|
||||||
fs_uae_wrapper
|
|
||||||
|
|
||||||
[bdist_wheel]
|
|
||||||
universal = 1
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
pytest
|
|
||||||
pytest-cov
|
|
||||||
pytest-pep8
|
|
||||||
coverage
|
|
||||||
flake8
|
|
||||||
@@ -1,15 +1,9 @@
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
from unittest import TestCase
|
from unittest import TestCase, mock
|
||||||
|
|
||||||
try:
|
from fs_uae_wrapper import archive, utils
|
||||||
from unittest import mock
|
|
||||||
except ImportError:
|
|
||||||
import mock
|
|
||||||
|
|
||||||
from fs_uae_wrapper import archive
|
|
||||||
from fs_uae_wrapper import utils
|
|
||||||
|
|
||||||
|
|
||||||
class TestArchive(TestCase):
|
class TestArchive(TestCase):
|
||||||
@@ -26,23 +20,26 @@ class TestArchive(TestCase):
|
|||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase._get_wrapper_archive_name')
|
||||||
@mock.patch('fs_uae_wrapper.path.which')
|
@mock.patch('fs_uae_wrapper.path.which')
|
||||||
def test_validate_options(self, which):
|
def test_validate_options(self, which, get_wrapper_arch_name):
|
||||||
which.return_value = 'unrar'
|
which.return_value = 'unrar'
|
||||||
|
|
||||||
arch = archive.Wrapper('Config.fs-uae', utils.CmdOption(), {})
|
arch = archive.Wrapper('Config.fs-uae', utils.CmdOption(), {})
|
||||||
self.assertFalse(arch._validate_options())
|
self.assertFalse(arch._validate_options())
|
||||||
arch.all_options = {'wrapper': 'archive'}
|
|
||||||
|
|
||||||
arch.all_options['wrapper'] = 'archive'
|
get_wrapper_arch_name.return_value = None
|
||||||
|
arch.all_options = {'wrapper': 'archive'}
|
||||||
self.assertFalse(arch._validate_options())
|
self.assertFalse(arch._validate_options())
|
||||||
|
|
||||||
|
get_wrapper_arch_name.return_value = 'fake_arch_filename'
|
||||||
arch.all_options['wrapper_archive'] = 'rar'
|
arch.all_options['wrapper_archive'] = 'rar'
|
||||||
self.assertTrue(arch._validate_options())
|
self.assertTrue(arch._validate_options())
|
||||||
|
|
||||||
@mock.patch('tempfile.mkdtemp')
|
@mock.patch('tempfile.mkdtemp')
|
||||||
@mock.patch('fs_uae_wrapper.path.which')
|
@mock.patch('fs_uae_wrapper.path.which')
|
||||||
@mock.patch('fs_uae_wrapper.archive.Wrapper._make_archive')
|
@mock.patch('fs_uae_wrapper.archive.Wrapper._make_archive')
|
||||||
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase._get_wrapper_archive_name')
|
||||||
@mock.patch('fs_uae_wrapper.base.ArchiveBase._save_save')
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase._save_save')
|
||||||
@mock.patch('fs_uae_wrapper.base.ArchiveBase._get_saves_dir')
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase._get_saves_dir')
|
||||||
@mock.patch('fs_uae_wrapper.base.ArchiveBase._run_emulator')
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase._run_emulator')
|
||||||
@@ -50,7 +47,8 @@ class TestArchive(TestCase):
|
|||||||
@mock.patch('fs_uae_wrapper.base.ArchiveBase._load_save')
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase._load_save')
|
||||||
@mock.patch('fs_uae_wrapper.base.ArchiveBase._extract')
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase._extract')
|
||||||
def test_run(self, extract, load_save, copy_conf, run_emulator,
|
def test_run(self, extract, load_save, copy_conf, run_emulator,
|
||||||
get_save_dir, save_state, make_arch, which, mkdtemp):
|
get_save_dir, save_state, get_wrapper_arch_name, make_arch,
|
||||||
|
which, mkdtemp):
|
||||||
|
|
||||||
extract.return_value = False
|
extract.return_value = False
|
||||||
load_save.return_value = False
|
load_save.return_value = False
|
||||||
@@ -58,6 +56,7 @@ class TestArchive(TestCase):
|
|||||||
run_emulator.return_value = False
|
run_emulator.return_value = False
|
||||||
get_save_dir.return_value = False
|
get_save_dir.return_value = False
|
||||||
save_state.return_value = False
|
save_state.return_value = False
|
||||||
|
get_wrapper_arch_name.return_value = "fake_arch_filename"
|
||||||
make_arch.return_value = False
|
make_arch.return_value = False
|
||||||
which.return_value = 'rar'
|
which.return_value = 'rar'
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,10 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import shutil
|
import shutil
|
||||||
from tempfile import mkstemp, mkdtemp
|
import sys
|
||||||
from unittest import TestCase
|
from tempfile import mkdtemp, mkstemp
|
||||||
|
from unittest import TestCase, mock
|
||||||
|
|
||||||
try:
|
from fs_uae_wrapper import base, utils
|
||||||
from unittest import mock
|
|
||||||
except ImportError:
|
|
||||||
import mock
|
|
||||||
|
|
||||||
from fs_uae_wrapper import base
|
|
||||||
from fs_uae_wrapper import utils
|
|
||||||
|
|
||||||
|
|
||||||
class TestBase(TestCase):
|
class TestBase(TestCase):
|
||||||
@@ -94,6 +88,11 @@ class TestBase(TestCase):
|
|||||||
bobj._normalize_options()
|
bobj._normalize_options()
|
||||||
self.assertDictEqual(bobj.fsuae_options, {})
|
self.assertDictEqual(bobj.fsuae_options, {})
|
||||||
|
|
||||||
|
get_config.return_value = {'random_item': 10}
|
||||||
|
bobj.fsuae_options = utils.CmdOption()
|
||||||
|
bobj._normalize_options()
|
||||||
|
self.assertDictEqual(bobj.fsuae_options, {})
|
||||||
|
|
||||||
@mock.patch('os.path.exists')
|
@mock.patch('os.path.exists')
|
||||||
@mock.patch('fs_uae_wrapper.utils.get_config')
|
@mock.patch('fs_uae_wrapper.utils.get_config')
|
||||||
def test_normalize_options_path_not_exists(self, get_config, os_exists):
|
def test_normalize_options_path_not_exists(self, get_config, os_exists):
|
||||||
@@ -286,7 +285,7 @@ class TestBase(TestCase):
|
|||||||
which.return_value = '7z'
|
which.return_value = '7z'
|
||||||
bobj.all_options = {'wrapper': 'dummy',
|
bobj.all_options = {'wrapper': 'dummy',
|
||||||
'wrapper_save_state': '1'}
|
'wrapper_save_state': '1'}
|
||||||
self.assertFalse(bobj._validate_options())
|
self.assertTrue(bobj._validate_options())
|
||||||
|
|
||||||
bobj.all_options = {'wrapper': 'dummy',
|
bobj.all_options = {'wrapper': 'dummy',
|
||||||
'wrapper_save_state': '1',
|
'wrapper_save_state': '1',
|
||||||
@@ -390,16 +389,42 @@ class TestArchiveBase(TestCase):
|
|||||||
self.assertFalse(bobj._extract())
|
self.assertFalse(bobj._extract())
|
||||||
utils_extract.assert_called_once_with(self.fname, '')
|
utils_extract.assert_called_once_with(self.fname, '')
|
||||||
|
|
||||||
def test_validate_options(self):
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase._get_wrapper_archive_name')
|
||||||
|
def test_validate_options(self, get_wrapper_arch_name):
|
||||||
|
|
||||||
bobj = base.ArchiveBase('Config.fs-uae', utils.CmdOption(), {})
|
bobj = base.ArchiveBase('Config.fs-uae', utils.CmdOption(), {})
|
||||||
bobj.all_options = {}
|
bobj.all_options = {}
|
||||||
|
|
||||||
self.assertFalse(bobj._validate_options())
|
self.assertFalse(bobj._validate_options())
|
||||||
|
|
||||||
|
get_wrapper_arch_name.return_value = None
|
||||||
bobj.all_options = {'wrapper': 'dummy'}
|
bobj.all_options = {'wrapper': 'dummy'}
|
||||||
self.assertFalse(bobj._validate_options())
|
self.assertFalse(bobj._validate_options())
|
||||||
|
|
||||||
bobj.all_options = {'wrapper': 'dummy',
|
bobj.all_options = {'wrapper': 'dummy',
|
||||||
'wrapper_archive': 'myarchive.7z'}
|
'wrapper_archive': 'myarchive.7z'}
|
||||||
self.assertTrue(bobj._validate_options())
|
self.assertTrue(bobj._validate_options())
|
||||||
|
|
||||||
|
@mock.patch('os.listdir')
|
||||||
|
def test_get_wrapper_archive_name(self, os_listdir):
|
||||||
|
os_listdir.return_value = 'no archive among other files'.split()
|
||||||
|
bobj = base.ArchiveBase('Config.fs-uae', utils.CmdOption(), {})
|
||||||
|
bobj.all_options = {'wrapper': 'dummy'}
|
||||||
|
self.assertIsNone(bobj._get_wrapper_archive_name())
|
||||||
|
|
||||||
|
os_listdir.return_value = 'no config.rar among other files'.split()
|
||||||
|
bobj = base.ArchiveBase('Config.fs-uae', utils.CmdOption(), {})
|
||||||
|
bobj.all_options = {'wrapper': 'dummy'}
|
||||||
|
self.assertIsNone(bobj._get_wrapper_archive_name())
|
||||||
|
|
||||||
|
os_listdir.return_value = 'file Config.TAR among other files'.split()
|
||||||
|
bobj = base.ArchiveBase('Config.fs-uae', utils.CmdOption(), {})
|
||||||
|
bobj.all_options = {'wrapper': 'dummy'}
|
||||||
|
self.assertEqual(bobj._get_wrapper_archive_name(), 'Config.TAR')
|
||||||
|
|
||||||
|
os_listdir.return_value = 'Config.lha FooBar_1.24b_20202.7z'.split()
|
||||||
|
bobj = base.ArchiveBase('FooBar_1.24b_20202.fs-uae',
|
||||||
|
utils.CmdOption(), {})
|
||||||
|
bobj.all_options = {'wrapper': 'dummy'}
|
||||||
|
self.assertEqual(bobj._get_wrapper_archive_name(),
|
||||||
|
'FooBar_1.24b_20202.7z')
|
||||||
|
|||||||
@@ -1,18 +1,13 @@
|
|||||||
from unittest import TestCase
|
from unittest import TestCase, mock
|
||||||
|
|
||||||
try:
|
from fs_uae_wrapper import cd32, utils
|
||||||
from unittest import mock
|
|
||||||
except ImportError:
|
|
||||||
import mock
|
|
||||||
|
|
||||||
from fs_uae_wrapper import cd32
|
|
||||||
from fs_uae_wrapper import utils
|
|
||||||
|
|
||||||
|
|
||||||
class TestCD32(TestCase):
|
class TestCD32(TestCase):
|
||||||
|
|
||||||
@mock.patch('tempfile.mkdtemp')
|
@mock.patch('tempfile.mkdtemp')
|
||||||
@mock.patch('fs_uae_wrapper.path.which')
|
@mock.patch('fs_uae_wrapper.path.which')
|
||||||
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase._get_wrapper_archive_name')
|
||||||
@mock.patch('fs_uae_wrapper.base.ArchiveBase._save_save')
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase._save_save')
|
||||||
@mock.patch('fs_uae_wrapper.base.ArchiveBase._get_saves_dir')
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase._get_saves_dir')
|
||||||
@mock.patch('fs_uae_wrapper.base.ArchiveBase._run_emulator')
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase._run_emulator')
|
||||||
@@ -20,7 +15,8 @@ class TestCD32(TestCase):
|
|||||||
@mock.patch('fs_uae_wrapper.base.ArchiveBase._load_save')
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase._load_save')
|
||||||
@mock.patch('fs_uae_wrapper.base.ArchiveBase._extract')
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase._extract')
|
||||||
def test_run(self, extract, load_save, copy_conf, run_emulator,
|
def test_run(self, extract, load_save, copy_conf, run_emulator,
|
||||||
get_save_dir, save_state, which, mkdtemp):
|
get_save_dir, save_state, get_wrapper_arch_name, which,
|
||||||
|
mkdtemp):
|
||||||
|
|
||||||
extract.return_value = False
|
extract.return_value = False
|
||||||
copy_conf.return_value = False
|
copy_conf.return_value = False
|
||||||
@@ -28,6 +24,7 @@ class TestCD32(TestCase):
|
|||||||
run_emulator.return_value = False
|
run_emulator.return_value = False
|
||||||
get_save_dir.return_value = False
|
get_save_dir.return_value = False
|
||||||
save_state.return_value = False
|
save_state.return_value = False
|
||||||
|
get_wrapper_arch_name.return_value = "fake_arch_filename"
|
||||||
which.return_value = 'unrar'
|
which.return_value = 'unrar'
|
||||||
|
|
||||||
acd32 = cd32.Wrapper('Config.fs-uae', utils.CmdOption(), {})
|
acd32 = cd32.Wrapper('Config.fs-uae', utils.CmdOption(), {})
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
from unittest import TestCase
|
from unittest import TestCase, mock
|
||||||
|
|
||||||
try:
|
|
||||||
from unittest import mock
|
|
||||||
except ImportError:
|
|
||||||
import mock
|
|
||||||
|
|
||||||
from fs_uae_wrapper import file_archive
|
from fs_uae_wrapper import file_archive
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,12 @@
|
|||||||
from unittest import TestCase
|
|
||||||
import os
|
import os
|
||||||
|
from unittest import TestCase, mock
|
||||||
try:
|
|
||||||
from unittest import mock
|
|
||||||
except ImportError:
|
|
||||||
import mock
|
|
||||||
|
|
||||||
from fs_uae_wrapper import message
|
from fs_uae_wrapper import message
|
||||||
|
from fs_uae_wrapper import nogui_message
|
||||||
|
|
||||||
if os.environ.get('DISPLAY'):
|
if os.environ.get('DISPLAY'):
|
||||||
try:
|
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
except ImportError:
|
|
||||||
import Tkinter as tk
|
|
||||||
import ttk
|
|
||||||
|
|
||||||
|
|
||||||
class TestMessage(TestCase):
|
class TestMessage(TestCase):
|
||||||
@@ -47,6 +39,19 @@ class TestMessage(TestCase):
|
|||||||
msg._process.join.assert_called_once()
|
msg._process.join.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
class TestNOPMessage(TestCase):
|
||||||
|
|
||||||
|
@mock.patch('sys.stdout.write')
|
||||||
|
def test_show(self, stdout_write):
|
||||||
|
msg = nogui_message.Message('display that')
|
||||||
|
msg.show()
|
||||||
|
stdout_write.assert_called_once()
|
||||||
|
|
||||||
|
def test_close(self):
|
||||||
|
msg = nogui_message.Message('display that')
|
||||||
|
self.assertIsNone(msg.close())
|
||||||
|
|
||||||
|
|
||||||
if os.environ.get('DISPLAY'):
|
if os.environ.get('DISPLAY'):
|
||||||
# Tkinter needs graphic environment for the widgets
|
# Tkinter needs graphic environment for the widgets
|
||||||
class TestSpawn(TestCase):
|
class TestSpawn(TestCase):
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
from unittest import TestCase
|
from unittest import TestCase, mock
|
||||||
|
|
||||||
try:
|
from fs_uae_wrapper import plain, utils
|
||||||
from unittest import mock
|
|
||||||
except ImportError:
|
|
||||||
import mock
|
|
||||||
|
|
||||||
from fs_uae_wrapper import plain
|
|
||||||
from fs_uae_wrapper import utils
|
|
||||||
|
|
||||||
|
|
||||||
class TestPlainModule(TestCase):
|
class TestPlainModule(TestCase):
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
from unittest import TestCase
|
from unittest import TestCase, mock
|
||||||
|
|
||||||
try:
|
from fs_uae_wrapper import savestate, utils
|
||||||
from unittest import mock
|
|
||||||
except ImportError:
|
|
||||||
import mock
|
|
||||||
|
|
||||||
from fs_uae_wrapper import savestate
|
|
||||||
from fs_uae_wrapper import utils
|
|
||||||
|
|
||||||
|
|
||||||
class TestSaveState(TestCase):
|
class TestSaveState(TestCase):
|
||||||
@@ -73,7 +67,7 @@ class TestSaveState(TestCase):
|
|||||||
self.assertFalse(arch._validate_options())
|
self.assertFalse(arch._validate_options())
|
||||||
|
|
||||||
arch.all_options['wrapper'] = 'savestate'
|
arch.all_options['wrapper'] = 'savestate'
|
||||||
self.assertFalse(arch._validate_options())
|
self.assertTrue(arch._validate_options())
|
||||||
|
|
||||||
arch.all_options['wrapper_archiver'] = 'rar'
|
arch.all_options['wrapper_archiver'] = 'rar'
|
||||||
self.assertTrue(arch._validate_options())
|
self.assertTrue(arch._validate_options())
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
from tempfile import mkstemp, mkdtemp
|
|
||||||
from unittest import TestCase
|
|
||||||
import shutil
|
import shutil
|
||||||
|
import sys
|
||||||
try:
|
from tempfile import mkdtemp, mkstemp
|
||||||
from unittest import mock
|
from unittest import TestCase, mock
|
||||||
except ImportError:
|
|
||||||
import mock
|
|
||||||
|
|
||||||
from fs_uae_wrapper import utils
|
from fs_uae_wrapper import utils
|
||||||
|
|
||||||
@@ -237,8 +232,8 @@ class TestCmdOptions(TestCase):
|
|||||||
@mock.patch('os.path.exists')
|
@mock.patch('os.path.exists')
|
||||||
@mock.patch('os.getenv')
|
@mock.patch('os.getenv')
|
||||||
@mock.patch('os.path.expandvars')
|
@mock.patch('os.path.expandvars')
|
||||||
@mock.patch('distutils.spawn.find_executable')
|
@mock.patch('shutil.which')
|
||||||
def test_interpolate_variables(self, find_exe, expandv, getenv, os_exists):
|
def test_interpolate_variables(self, which, expandv, getenv, os_exists):
|
||||||
|
|
||||||
os_exists.return_value = True
|
os_exists.return_value = True
|
||||||
itrpl = utils.interpolate_variables
|
itrpl = utils.interpolate_variables
|
||||||
@@ -252,7 +247,7 @@ class TestCmdOptions(TestCase):
|
|||||||
'/home/user')
|
'/home/user')
|
||||||
|
|
||||||
string = '$APP/$EXE'
|
string = '$APP/$EXE'
|
||||||
find_exe.return_value = '/usr/bin/fs-uae'
|
which.return_value = '/usr/bin/fs-uae'
|
||||||
self.assertEqual(itrpl(string, '/home/user/Config.fs-uae'),
|
self.assertEqual(itrpl(string, '/home/user/Config.fs-uae'),
|
||||||
'/usr/bin/fs-uae//usr/bin/fs-uae')
|
'/usr/bin/fs-uae//usr/bin/fs-uae')
|
||||||
|
|
||||||
|
|||||||
233
tests/test_whdload.py
Normal file
233
tests/test_whdload.py
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
from tempfile import mkdtemp
|
||||||
|
from unittest import TestCase, mock
|
||||||
|
|
||||||
|
from fs_uae_wrapper import utils, whdload
|
||||||
|
|
||||||
|
|
||||||
|
class TestWHDLoad(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.dirname = mkdtemp()
|
||||||
|
self.curdir = os.path.abspath(os.curdir)
|
||||||
|
os.chdir(self.dirname)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
os.chdir(self.curdir)
|
||||||
|
try:
|
||||||
|
shutil.rmtree(self.dirname)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase._validate_options')
|
||||||
|
def test_validate_options_arch_validation_fail(self, base_valid):
|
||||||
|
|
||||||
|
base_valid.return_value = False
|
||||||
|
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
|
||||||
|
self.assertFalse(wrapper._validate_options())
|
||||||
|
|
||||||
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase._validate_options')
|
||||||
|
def test_validate_options_no_base_image(self, base_valid):
|
||||||
|
|
||||||
|
base_valid.return_value = True
|
||||||
|
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
|
||||||
|
self.assertFalse(wrapper._validate_options())
|
||||||
|
|
||||||
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase._validate_options')
|
||||||
|
def test_validate_options_with_base_image_set(self, base_valid):
|
||||||
|
|
||||||
|
base_valid.return_value = True
|
||||||
|
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
|
||||||
|
wrapper.all_options['wrapper_whdload_base'] = 'fake_base_fname.7z'
|
||||||
|
self.assertTrue(wrapper._validate_options())
|
||||||
|
|
||||||
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase.run')
|
||||||
|
def test_run_base_run_fail(self, run):
|
||||||
|
|
||||||
|
run.return_value = False
|
||||||
|
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
|
||||||
|
self.assertFalse(wrapper.run())
|
||||||
|
|
||||||
|
@mock.patch('fs_uae_wrapper.whdload.Wrapper._extract')
|
||||||
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase.run')
|
||||||
|
def test_run_extract_fail(self, run, extract):
|
||||||
|
|
||||||
|
run.return_value = True
|
||||||
|
extract.return_value = False
|
||||||
|
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
|
||||||
|
wrapper.all_options = {'wrapper': 'whdload',
|
||||||
|
'wrapper_archive': 'fake.tgz',
|
||||||
|
'wrapper_archiver': 'rar'}
|
||||||
|
self.assertFalse(wrapper.run())
|
||||||
|
|
||||||
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase._copy_conf')
|
||||||
|
@mock.patch('fs_uae_wrapper.whdload.Wrapper._extract')
|
||||||
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase.run')
|
||||||
|
def test_run_copy_conf_fail(self, run, extract, copy_conf):
|
||||||
|
|
||||||
|
run.return_value = True
|
||||||
|
extract.return_value = True
|
||||||
|
copy_conf.return_value = False
|
||||||
|
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
|
||||||
|
self.assertFalse(wrapper.run())
|
||||||
|
|
||||||
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase._run_emulator')
|
||||||
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase._copy_conf')
|
||||||
|
@mock.patch('fs_uae_wrapper.whdload.Wrapper._extract')
|
||||||
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase.run')
|
||||||
|
def test_run_emulator_fail(self, run, extract, copy_conf, run_emulator):
|
||||||
|
|
||||||
|
run.return_value = True
|
||||||
|
extract.return_value = True
|
||||||
|
copy_conf.return_value = True
|
||||||
|
run_emulator.return_value = False
|
||||||
|
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
|
||||||
|
self.assertFalse(wrapper.run())
|
||||||
|
|
||||||
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase._run_emulator')
|
||||||
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase._copy_conf')
|
||||||
|
@mock.patch('fs_uae_wrapper.whdload.Wrapper._extract')
|
||||||
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase.run')
|
||||||
|
def test_run_success(self, run, extract, copy_conf, run_emulator):
|
||||||
|
|
||||||
|
run.return_value = True
|
||||||
|
extract.return_value = True
|
||||||
|
copy_conf.return_value = True
|
||||||
|
run_emulator.return_value = True
|
||||||
|
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
|
||||||
|
self.assertTrue(wrapper.run())
|
||||||
|
|
||||||
|
@mock.patch('os.path.exists')
|
||||||
|
def test_extract_nonexistent_image(self, exists):
|
||||||
|
exists.return_value = False
|
||||||
|
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
|
||||||
|
wrapper.fsuae_options['wrapper_whdload_base'] = 'fakefilename'
|
||||||
|
self.assertFalse(wrapper._extract())
|
||||||
|
|
||||||
|
@mock.patch('os.chdir')
|
||||||
|
@mock.patch('os.path.exists')
|
||||||
|
def test_extract_extraction_failed(self, exists, chdir):
|
||||||
|
exists.return_value = True
|
||||||
|
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
|
||||||
|
wrapper.fsuae_options['wrapper_whdload_base'] = 'fakefilename.7z'
|
||||||
|
self.assertFalse(wrapper._extract())
|
||||||
|
|
||||||
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase._extract')
|
||||||
|
@mock.patch('fs_uae_wrapper.utils.extract_archive')
|
||||||
|
@mock.patch('os.chdir')
|
||||||
|
@mock.patch('os.path.exists')
|
||||||
|
def test_extract_extraction_of_whdload_arch_failed(self, exists, chdir,
|
||||||
|
image_extract,
|
||||||
|
arch_extract):
|
||||||
|
exists.return_value = True
|
||||||
|
image_extract.return_value = True
|
||||||
|
arch_extract.return_value = False
|
||||||
|
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
|
||||||
|
wrapper.fsuae_options['wrapper_whdload_base'] = 'fakefilename'
|
||||||
|
self.assertFalse(wrapper._extract())
|
||||||
|
|
||||||
|
@mock.patch('fs_uae_wrapper.whdload.Wrapper._find_slave')
|
||||||
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase._extract')
|
||||||
|
@mock.patch('fs_uae_wrapper.utils.extract_archive')
|
||||||
|
@mock.patch('os.chdir')
|
||||||
|
@mock.patch('os.path.exists')
|
||||||
|
def test_extract_slave_not_found(self, exists, chdir, image_extract,
|
||||||
|
arch_extract, find_slave):
|
||||||
|
exists.return_value = True
|
||||||
|
image_extract.return_value = True
|
||||||
|
arch_extract.return_value = True
|
||||||
|
find_slave.return_value = False
|
||||||
|
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
|
||||||
|
wrapper.fsuae_options['wrapper_whdload_base'] = 'fakefilename'
|
||||||
|
self.assertFalse(wrapper._extract())
|
||||||
|
|
||||||
|
@mock.patch('fs_uae_wrapper.whdload.Wrapper._find_slave')
|
||||||
|
@mock.patch('fs_uae_wrapper.base.ArchiveBase._extract')
|
||||||
|
@mock.patch('fs_uae_wrapper.utils.extract_archive')
|
||||||
|
@mock.patch('os.chdir')
|
||||||
|
@mock.patch('os.path.exists')
|
||||||
|
def test_extract_success(self, exists, chdir, image_extract, arch_extract,
|
||||||
|
find_slave):
|
||||||
|
exists.return_value = True
|
||||||
|
image_extract.return_value = True
|
||||||
|
arch_extract.return_value = True
|
||||||
|
find_slave.return_value = True
|
||||||
|
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
|
||||||
|
wrapper.fsuae_options['wrapper_whdload_base'] = 'fakefilename'
|
||||||
|
self.assertTrue(wrapper._extract())
|
||||||
|
|
||||||
|
@mock.patch('os.walk')
|
||||||
|
@mock.patch('os.chdir')
|
||||||
|
def test_find_slave_no_slave_file(self, chdir, walk):
|
||||||
|
walk.return_value = [(".", ('game'), ()),
|
||||||
|
('./game', (), ('foo', 'bar', 'baz'))]
|
||||||
|
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
|
||||||
|
self.assertFalse(wrapper._find_slave())
|
||||||
|
|
||||||
|
@mock.patch('os.listdir')
|
||||||
|
@mock.patch('os.walk')
|
||||||
|
@mock.patch('os.chdir')
|
||||||
|
def test_find_slave_no_corresponding_icon(self, chdir, walk, listdir):
|
||||||
|
contents = ('foo', 'bar', 'baz.slave')
|
||||||
|
walk.return_value = [(".", ('game'), ()),
|
||||||
|
('./game', (), contents)]
|
||||||
|
listdir.return_value = contents
|
||||||
|
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
|
||||||
|
self.assertFalse(wrapper._find_slave())
|
||||||
|
|
||||||
|
@mock.patch('os.listdir')
|
||||||
|
@mock.patch('os.walk')
|
||||||
|
@mock.patch('os.chdir')
|
||||||
|
def test_find_slave_success(self, chdir, walk, listdir):
|
||||||
|
contents = ('foo', 'bar', 'baz.slave', 'baz.info')
|
||||||
|
_open = mock.mock_open()
|
||||||
|
walk.return_value = [(".", ('C', 'S', 'game'), ()),
|
||||||
|
('./C', (), ('Assign', 'kgiconload')),
|
||||||
|
('./S', (), ()),
|
||||||
|
('./game', (), contents)]
|
||||||
|
listdir.return_value = contents
|
||||||
|
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
|
||||||
|
with mock.patch('builtins.open', _open):
|
||||||
|
self.assertTrue(wrapper._find_slave())
|
||||||
|
handle = _open()
|
||||||
|
handle.write.assert_called_once_with('cd game\n'
|
||||||
|
'C:kgiconload baz.info\n')
|
||||||
|
|
||||||
|
@mock.patch('os.listdir')
|
||||||
|
@mock.patch('os.walk')
|
||||||
|
@mock.patch('os.chdir')
|
||||||
|
def test_find_slave_minial(self, chdir, walk, listdir):
|
||||||
|
contents = ('foo', 'bar', 'baz.slave', 'baz.info')
|
||||||
|
_open = mock.mock_open()
|
||||||
|
walk.return_value = [(".", ('C', 'S', 'game'), ()),
|
||||||
|
('./C', (), ('Assign', 'WHDLoad')),
|
||||||
|
('./S', (), ()),
|
||||||
|
('./game', (), contents)]
|
||||||
|
listdir.return_value = contents
|
||||||
|
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
|
||||||
|
with mock.patch('builtins.open', _open):
|
||||||
|
self.assertTrue(wrapper._find_slave())
|
||||||
|
handle = _open()
|
||||||
|
handle.write.assert_called_once_with('cd game\nC:whdload Preload '
|
||||||
|
'Slave=baz.slave\n')
|
||||||
|
|
||||||
|
@mock.patch('os.listdir')
|
||||||
|
@mock.patch('os.walk')
|
||||||
|
@mock.patch('os.chdir')
|
||||||
|
def test_find_custom_options(self, chdir, walk, listdir):
|
||||||
|
contents = ('foo', 'bar', 'baz.slave', 'baz.info')
|
||||||
|
_open = mock.mock_open()
|
||||||
|
walk.return_value = [(".", ('C', 'S', 'game'), ()),
|
||||||
|
('./C', (), ('Assign', 'WHDLoad')),
|
||||||
|
('./S', (), ()),
|
||||||
|
('./game', (), contents)]
|
||||||
|
listdir.return_value = contents
|
||||||
|
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
|
||||||
|
whdl_opts = 'Preload SplashDelay=0 MMU PAL'
|
||||||
|
wrapper.all_options['wrapper_whdload_options'] = whdl_opts
|
||||||
|
with mock.patch('builtins.open', _open):
|
||||||
|
self.assertTrue(wrapper._find_slave())
|
||||||
|
handle = _open()
|
||||||
|
handle.write.assert_called_once_with(f'cd game\nC:whdload {whdl_opts} '
|
||||||
|
'Slave=baz.slave\n')
|
||||||
@@ -1,13 +1,8 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
from tempfile import mkstemp, mkdtemp
|
|
||||||
from unittest import TestCase
|
|
||||||
import shutil
|
import shutil
|
||||||
|
import sys
|
||||||
try:
|
from tempfile import mkdtemp, mkstemp
|
||||||
from unittest import mock
|
from unittest import TestCase, mock
|
||||||
except ImportError:
|
|
||||||
import mock
|
|
||||||
|
|
||||||
from fs_uae_wrapper import wrapper
|
from fs_uae_wrapper import wrapper
|
||||||
|
|
||||||
@@ -47,10 +42,7 @@ class TestWrapper(TestCase):
|
|||||||
fobj.write('\n')
|
fobj.write('\n')
|
||||||
|
|
||||||
wrapper.run()
|
wrapper.run()
|
||||||
mock_plain_run.called_once_with('Config.fs-uae',
|
mock_plain_run.assert_called_once()
|
||||||
['--fullscreen',
|
|
||||||
'--fade_out_duration=0'],
|
|
||||||
[])
|
|
||||||
|
|
||||||
# This will obviously fail for nonexistent module
|
# This will obviously fail for nonexistent module
|
||||||
sys.argv.append('--wrapper=dummy_wrapper')
|
sys.argv.append('--wrapper=dummy_wrapper')
|
||||||
|
|||||||
7
tox.ini
7
tox.ini
@@ -8,7 +8,12 @@ usedevelop=True
|
|||||||
setenv = COVERAGE_FILE = .coverage
|
setenv = COVERAGE_FILE = .coverage
|
||||||
commands = py.test --cov=fs_uae_wrapper --cov-report=term-missing
|
commands = py.test --cov=fs_uae_wrapper --cov-report=term-missing
|
||||||
|
|
||||||
deps = -r{toxinidir}/test-requirements.txt
|
deps =
|
||||||
|
pytest
|
||||||
|
pytest-cov
|
||||||
|
pytest-pep8
|
||||||
|
coverage
|
||||||
|
flake8
|
||||||
|
|
||||||
[testenv:py3-flake8]
|
[testenv:py3-flake8]
|
||||||
basepython = python3
|
basepython = python3
|
||||||
|
|||||||
Reference in New Issue
Block a user