diff --git a/cdemu/__init__.py b/cdemu/__init__.py new file mode 100644 --- /dev/null +++ b/cdemu/__init__.py @@ -0,0 +1,231 @@ +from event.sync import EventDispatcher +from configparser import ConfigParser +import os +from .device import CDEmuDevice +from .proxy import CDEmuDaemonProxy, OP_DEV_ID, OP_DPM_EMU, OP_TR_EMU, OP_BS_EMU +from mount import mount + + +class CDEmuException(Exception): + """ + Exception class that encompasses exceptions happening while interacting with CDEmu class + """ + pass + + +class CDEmu(EventDispatcher): + def __init__(self, state_file=None, config=None): + """ + High-level abstraction for CDEmu daemon. Includes following features: + 1) Automatically restart daemon if it is stopped(fails?) + 2) Automatically unmount device before ejecting or removing it + 3) Automatically restore daemon state on startup + """ + super().__init__( + 'daemon_started', + 'daemon_stopped', + 'device_status_changed', + 'device_option_changed', + 'devices_changed' + ) + + self.devices = [] + + self.state_file = state_file + self.state = ConfigParser() + if state_file and os.path.isfile(state_file): + self.state.read(state_file) + + if not self.state.has_section('main'): + self.change_option('main', 'devices', '1') + self.change_option('main', 'autorestart', '1') + self.change_option('dev0', 'dpm-emulation', '0') + self.change_option('dev0', 'tr-emulation', '0') + self.change_option('dev0', 'bad-sector-emulation', '0') + self.save_options() + + self.daemon = CDEmuDaemonProxy() + self.daemon.add_handler('daemon_started', self.on_daemon_started) + self.daemon.add_handler('daemon_stopped', self.on_daemon_stopped) + self.daemon.add_handler('device_mapping_ready', self.on_device_mapping_ready) + self.daemon.add_handler('device_added', self.on_device_added) + self.daemon.add_handler('device_removed', self.on_device_removed) + self.daemon.add_handler('device_status_changed', self.on_device_status_changed) + self.daemon.add_handler('device_option_changed', self.on_device_option_changed) + if self.daemon.is_running: + self.on_daemon_started() + + def get_option(self, section, option): + if not self.state.has_section(section): + return None + if option not in self.state[section]: + return None + value = self.state[section][option] + if value.startswith('"'): + value = value[1:-1] + return value + + def change_option(self, section, option, value, save=False): + value = str(value) + if value.startswith(' ') or value.startswith('"') or value.endswith(' '): + value = '"{}"'.format(value) + if not self.state.has_section(section): + self.state.add_section(section) + self.state.set(section, option, value) + if save and self.state_file: + with open(self.state_file, 'w') as f: + self.state.write(f) + + def save_options(self): + if self.state_file: + with open(self.state_file, 'w') as f: + self.state.write(f) + + def on_daemon_started(self): + self.dispatch('daemon_started') + need_devices = int(self.get_option('main', 'devices')) + have_devices = self.daemon.get_number_of_devices() + while have_devices < need_devices: + self.daemon.add_device() + have_devices += 1 + while have_devices > need_devices: + self.daemon.remove_device() + have_devices -= 1 + for x in range(need_devices): + if len(self.devices) <= x: + self.devices.append(self._create_device(len(self.devices))) + + if self.state.has_section('dev{}'.format(x)): + sect = 'dev{}'.format(x) + else: + sect = 'main' + if self.get_option(sect, OP_DPM_EMU): + self.daemon.device_set_option(x, OP_DPM_EMU, self.get_option(sect, OP_DPM_EMU) == '1') + if self.get_option(sect, OP_TR_EMU): + self.daemon.device_set_option(x, OP_TR_EMU, self.get_option(sect, OP_TR_EMU) == '1') + if self.get_option(sect, OP_BS_EMU): + self.daemon.device_set_option(x, OP_BS_EMU, self.get_option(sect, OP_BS_EMU) == '1') + if self.get_option(sect, 'vendor'): + vendor = self.get_option(sect, 'vendor') + product = self.get_option(sect, 'product') + revision = self.get_option(sect, 'revision') + vendor_specific = self.get_option(sect, 'vendor_specific') + self.daemon.device_set_option(x, OP_DEV_ID, (vendor, product, revision, vendor_specific)) + if self.get_option(sect, 'img'): + self.load(self.devices[x], self.get_option(sect, 'img')) + else: + self.unload(self.devices[x]) + + sr_map, sg_map = self.daemon.device_get_mapping(x) + if sr_map or sg_map: + self.on_device_mapping_ready(x) + + def on_daemon_stopped(self): + self.dispatch('daemon_stopped') + if self.get_option('main', 'autorestart') == '1': + self.daemon.connect() + + def on_device_mapping_ready(self, number): + if number >= len(self.devices): + return + + device = self.devices[number] + device._mapped_sr, device._mapped_sg = self.daemon.device_get_mapping(number) + loaded, img = self.daemon.device_get_status(number) + if loaded and len(img) > 0: + device._img = img[0] + + device._dpm = self.daemon.device_get_option(number, 'dpm-emulation') + device._tr = self.daemon.device_get_option(number, 'tr-emulation') + device._bs = self.daemon.device_get_option(number, 'bad-sector-emulation') + device._vendor, device._product, device._revision, device._vendor_specific = self.daemon.device_get_option( + number, 'device-id') + + section = 'dev{}'.format(number) + self.change_option(section, OP_DPM_EMU, device._dpm) + self.change_option(section, OP_TR_EMU, device._tr) + self.change_option(section, OP_BS_EMU, device._bs) + + self.change_option(section, 'vendor', device._vendor) + self.change_option(section, 'product', device._product) + self.change_option(section, 'revision', device._revision) + self.change_option(section, 'vendor_specific', device._vendor_specific) + self.save_options() + + self.dispatch('devices_changed', number) + + def _create_device(self, id): + device = CDEmuDevice(id, self) + return device + + def load(self, device, img): + self.daemon.device_load(device._device_id, [img], {}) + + def unload(self, device): + try: + if device.mapped_sr and mount.is_mounted(device.mapped_sr): + mount.unmount(device.mapped_sr) + except mount.MountException as e: + raise CDEmuException(str(e)) + self.daemon.device_unload(device._device_id) + + def on_device_added(self): + # Add device and send event + devices = self.daemon.get_number_of_devices() + self.change_option('main', 'devices', devices, True) + while len(self.devices) < devices: + self.devices.append(self._create_device(len(self.devices))) + + def on_device_removed(self): + # Remove device and send event + devices = self.daemon.get_number_of_devices() + while len(self.devices) > devices: + self.devices.pop() + self.dispatch('devices_changed') + + def on_device_status_changed(self, id): + loaded, img = self.daemon.device_get_status(id) + if loaded and len(img) > 0: + self.devices[id]._img = img[0] + self.change_option('dev{}'.format(id), 'img', self.devices[id]._img, True) + else: + self.devices[id]._img = None + self.change_option('dev{}'.format(id), 'img', '', True) + self.devices[id].dispatch('change') + + def on_device_option_changed(self, id, option): + value = self.daemon.device_get_option(id, option) + section = 'dev{}'.format(id) + if option == OP_DPM_EMU: + self.devices[id]._dpm = value + self.change_option(section, OP_DPM_EMU, value) + elif option == OP_TR_EMU: + self.devices[id]._tr = value + self.change_option(section, OP_TR_EMU, value) + elif option == OP_BS_EMU: + self.devices[id]._bs = value + self.change_option(section, OP_BS_EMU, value) + elif option == OP_DEV_ID: + self.devices[id]._vendor, self.devices[id]._product, \ + self.devices[id]._revision, self.devices[id]._vendor_specific = value + self.change_option(section, 'vendor', self.devices[id]._vendor) + self.change_option(section, 'product', self.devices[id]._product) + self.change_option(section, 'revision', self.devices[id]._revision) + self.change_option(section, 'vendor_specific', self.devices[id]._vendor_specific) + self.save_options() + self.devices[id].dispatch('change') + + def set_device_option(self, device_number, option_name, option_value): + self.daemon.device_set_option(device_number, option_name, option_value) + + def add_device(self): + self.daemon.add_device() + + def remove_device(self): + self.daemon.remove_device() + + def get_devices(self): + return self.devices.copy() + + def is_daemon_ready(self): + return self.daemon.is_running \ No newline at end of file