|
new file 100644
|
|
|
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
|