Files @ b168d439d9a0
Branch filter:

Location: linux-tools/kcdemu/cdemu/__init__.py

Silverwing
initial commit. Version 1.0
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