""" Base class for all wrapper modules """ import logging import os import shutil import tempfile from fs_uae_wrapper import path, utils class Base(object): """ Base class for wrapper modules """ def __init__(self, conf_file, fsuae_options, configuration): """ Params: conf_file: a relative path to provided configuration file fsuae_options: is an CmdOption object created out of command line parameters configuration: is config dictionary created out of config file """ self.conf_file = conf_file self.fsuae_config = configuration self.fsuae_options = fsuae_options self.all_options = utils.merge_all_options(configuration, fsuae_options) self.dir = None self.save_filename = None def run(self): """ Main function which accepts configuration file for FS-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 """ logging.debug("run") if not self._validate_options(): return False self.dir = tempfile.mkdtemp(prefix='fs-uae-wrapper-') self._normalize_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.all_options.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 Config.fs-uae""" 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, 'Config.fs-uae')) return True def _run_emulator(self): """execute fs-uae""" curdir = os.path.abspath('.') os.chdir(self.dir) utils.run_command(['fs-uae', *self.fsuae_options.list()]) os.chdir(curdir) return True def _get_title(self): """ Return the title if found in configuration. As a fallback archive file name will be used as title. """ title = '' gui_msg = self.all_options.get('wrapper_gui_msg', '0') if gui_msg == '1': title = self.all_options.get('title') if not title: title = self.all_options['wrapper_archive'] return title def _save_save(self): """ Get the saves from emulator and store it where configuration is placed """ if self.all_options.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.all_options.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.all_options.get('save_states_dir'): return None if self.all_options['save_states_dir'].startswith('$WRAPPER') and \ '..' not in self.all_options['save_states_dir']: save = self.all_options['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 _normalize_options(self): """ Search and replace values for options which starts with $CONFIG with absolute path for all options. Configuration file will be placed in new directory, therefore it is needed to calculate new paths so that emulator can find assets. """ logging.debug("_normalize_options") options = ['wrapper_archive', 'wrapper_whdload_base', 'accelerator_rom', 'base_dir', 'cdrom_drive_0', 'cdroms_dir', 'controllers_dir', 'cpuboard_flash_ext_file', 'cpuboard_flash_file', 'floppies_dir', 'floppy_overlays_dir', 'fmv_rom', 'graphics_card_rom', 'hard_drives_dir', 'kickstart_file', 'kickstarts_dir', 'logs_dir', 'save_states_dir', 'screenshots_output_dir'] for num in range(20): options.append('cdrom_image_%d' % num) options.append('floppy_image_%d' % num) for num in range(4): options.append('floppy_drive_%d' % num) for num in range(10): options.append('hard_drive_%d' % num) changed_options = {} for key, val in utils.get_config(self.conf_file).items(): if key not in options: continue if val.startswith('/'): continue if val.startswith('~'): continue if val.startswith('$HOME'): continue if val.startswith('$WRAPPER'): changed_options[key] = val.replace('$WRAPPER', self.dir) continue if val.startswith('$CONFIG'): abspath = utils.interpolate_variables(val, self.conf_file) changed_options[key] = abspath logging.info("%s: %s => %s", key, val, abspath) continue _val = os.path.abspath(val) if os.path.exists(_val): changed_options[key] = _val else: changed_options[key] = val self.fsuae_options.update(changed_options) def _validate_options(self): """Validate mandatory options""" if 'wrapper' not in self.all_options: logging.error("Configuration lacks of required `wrapper' option.") return False if self.all_options.get('wrapper_save_state', '0') == '0': return True if 'wrapper_archiver' not in self.all_options: logging.warning("Configuration lacks of optional " "`wrapper_archiver' option, fall back to 7z") self.all_options['wrapper_archiver'] = "7z" if not path.which(self.all_options['wrapper_archiver']): logging.error("Cannot find archiver `%s'.", self.all_options['wrapper_archiver']) return False return True class ArchiveBase(Base): """ Base class for archive based wrapper modules """ def __init__(self, conf_file, fsuae_options, configuration): """ Params: conf_file: a relative path to provided configuration file fsuae_options: is an CmdOption object created out of command line parameters configuration: is config dictionary created out of config file """ super(ArchiveBase, self).__init__(conf_file, fsuae_options, configuration) 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.all_options.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""" logging.debug("_extract") 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): logging.debug("_validate_options") validation_result = super(ArchiveBase, self)._validate_options() if not validation_result: return False if 'wrapper_archive' not in self.all_options: logging.warning("Configuration lacks of optional `wrapper_archive'" " 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 self.all_options['wrapper_archive'] = wrapper_archive 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