diff --git a/README.rst b/README.rst index 7056fbb..7cf8e18 100644 --- a/README.rst +++ b/README.rst @@ -106,6 +106,7 @@ Currently, three wrapper modules are available: - plain - cd32 - archive +- savestate plain ----- @@ -139,7 +140,6 @@ Let's see some sample config for a game, which is saved as ``ChaosEngine.fs-uae``: .. code:: ini - :number-lines: [config] wrapper = cd32 @@ -165,8 +165,7 @@ Next, the invocation of the wrapper would be as follows: Now, there several thing will happen: -- Config file will be read, and wrapper module will be find (because we already - put it on line 2) +- Config file will be read, and wrapper module will be found - New temporary directory will be created - Archive with game assists will be extracted in that directory - Configuration file will be copied into that directory, and renamed to @@ -204,7 +203,6 @@ Options used: Example configuration: .. code:: ini - :number-lines: [config] wrapper = archive @@ -215,6 +213,13 @@ Example configuration: wrapper_save_state = 1 ... +This module is quite useful in two use cases. First is a usual work with +Workbench, where there is a need to keep changes of filesystem. Second is the +opposite - if there is a need to test some software, but not necessary keep it +in a Workbench, than it will act as a temporary copy of the system, so that +next time fs-uae will be run, there will be no files of tested software +cluttering around. + And execution is as usual: .. code:: shell-session @@ -234,12 +239,53 @@ This module will do several steps (similar as with ``cd32`` wrapper): - optionally create new archive under the same name as the original one and replace it with original one. -This module is quite useful in two use cases. First is a usual work with -Workbench, where there is a need to keep changes of filesystem. Second is the -opposite - if there is a need to test some software, but not necessary keep it -in a Workbench, than it will act as a temporary copy of the system, so that -next time fs-uae will be run, there will be no files of tested software -cluttering around. +savestate +--------- + +Options used: + +* ``wrapper`` (required) with ``archive`` as an value +* ``wrapper_archiver`` (conditionally required) archiver to use for storage + save state +* ``wrapper_gui_msg`` (optional) if set to "1", will display a graphical + message during extracting files +* ``wrapper_save_state`` (optional) if set to "1", will archive save state + directory, defined as ``$CONFIG/[save-state-dir-name]`` using provided + ``wrapper_archiver`` archiver. If this option is enabled, + ``wrapper_archiver`` will be required. + +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 +preserve save state which will be created as an archive alongside with original +configuration file in selected archive format. + +Example configuration: + +.. code:: ini + :number-lines: + + [config] + wrapper = savestate + wrapper_archiver = 7z + wrapper_gui_msg = 1 + wrapper_save_state = 1 + ... + +And execution is as usual: + +.. code:: shell-session + + $ fs-uae-wrapper Sanity-Arte.fs-uae + +The steps would be as follows: + +- create temporary directory +- extract save state (if ``wrapper_save_state`` is set to ``1`` and archive + with save exists) +- copy configuration under name ``Config.fs-uae`` +- run the fs-uae emulator +- optionally create archive with save state (if save state directory place is + *not* a global one) License ======= diff --git a/fs_uae_wrapper/savestate.py b/fs_uae_wrapper/savestate.py new file mode 100644 index 0000000..1d3aa2b --- /dev/null +++ b/fs_uae_wrapper/savestate.py @@ -0,0 +1,50 @@ +""" +Wrapper module with preserved save state in archive file. +""" +from fs_uae_wrapper import base + + +class SaveState(base.Base): + """ + Preserve save state. + """ + def run(self): + """ + Main function which accepts configuration file for FS-UAE + It will do as follows: + - set needed full path for asset files + - copy configuration + - optionally extract save state archive + - run the emulation + - optionally make archive save state + """ + if not super(SaveState, self).run(): + return False + + self._load_save() + + if not self._copy_conf(): + return False + + kick_opts = self._kickstart_option() + if kick_opts: + self.fsuae_options.update(kick_opts) + + if not self._run_emulator(self.fsuae_options.list()): + return False + + if self._get_saves_dir(): + if not self._save_save(): + return False + + return True + + +def run(config_file, fsuae_options, configuration): + """Run fs-uae with provided config file and options""" + + runner = SaveState(config_file, fsuae_options, configuration) + try: + return runner.run() + finally: + runner.clean() diff --git a/tests/test_savestate.py b/tests/test_savestate.py new file mode 100644 index 0000000..dced671 --- /dev/null +++ b/tests/test_savestate.py @@ -0,0 +1,72 @@ +import os +import shutil +from tempfile import mkdtemp +from unittest import TestCase + +try: + from unittest import mock +except ImportError: + import mock + +from fs_uae_wrapper import savestate +from fs_uae_wrapper import utils + + +class TestArchive(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('tempfile.mkdtemp') + @mock.patch('fs_uae_wrapper.path.which') + @mock.patch('fs_uae_wrapper.base.Base._save_save') + @mock.patch('fs_uae_wrapper.base.Base._get_saves_dir') + @mock.patch('fs_uae_wrapper.base.Base._run_emulator') + @mock.patch('fs_uae_wrapper.base.Base._kickstart_option') + @mock.patch('fs_uae_wrapper.base.Base._copy_conf') + @mock.patch('fs_uae_wrapper.base.Base._load_save') + def test_run(self, load_save, copy_conf, kick_option, run_emulator, + get_save_dir, save_state, which, mkdtemp): + + copy_conf.return_value = False + kick_option.return_value = False + run_emulator.return_value = False + get_save_dir.return_value = False + save_state.return_value = False + which.return_value = 'rar' + + arch = savestate.SaveState('Config.fs-uae', utils.CmdOption(), {}) + self.assertFalse(arch.run()) + + arch.all_options = {'wrapper': 'archive', + 'wrapper_save_state': '1', + 'wrapper_archiver': 'rar'} + + self.assertFalse(arch.run()) + + self.assertFalse(arch.run()) + + copy_conf.return_value = True + self.assertFalse(arch.run()) + + kick_option.return_value = {'foo': 'bar'} + self.assertFalse(arch.run()) + self.assertDictEqual(arch.fsuae_options, {'foo': 'bar'}) + + run_emulator.return_value = True + self.assertTrue(arch.run()) + + get_save_dir.return_value = True + self.assertFalse(arch.run()) + + save_state.return_value = True + self.assertTrue(arch.run())