Esempio n. 1
0
    def __init__(self, argv=None):
        """
        Parse command line options, read config and initialize members.

        :param list argv: command line parameters
        """
        # parse program options (retrieve log level and config file name):
        args = docopt(self.usage, version=self.name + ' ' + self.version)
        default_opts = self.option_defaults
        program_opts = self.program_options(args)
        # initialize logging configuration:
        log_level = program_opts.get('log_level', default_opts['log_level'])
        if log_level <= logging.DEBUG:
            fmt = _('%(levelname)s [%(asctime)s] %(name)s: %(message)s')
        else:
            fmt = _('%(message)s')
        logging.basicConfig(level=log_level, format=fmt)
        # parse config options
        config_file = OptionalValue('--config')(args)
        config = udiskie.config.Config.from_file(config_file)
        options = {}
        options.update(default_opts)
        options.update(config.program_options)
        options.update(program_opts)
        # initialize instance variables
        self.config = config
        self.options = options
        self._init(config, options)
Esempio n. 2
0
def get_backend(clsname, version=None):
    """
    Return UDisks service.

    :param str clsname: requested service object
    :param int version: requested UDisks backend version
    :returns: UDisks service wrapper object
    :raises dbus.DBusException: if unable to connect to UDisks dbus service.
    :raises ValueError: if the version is invalid

    If ``version`` has a false truth value, try to connect to UDisks1 and
    fall back to UDisks2 if not available.
    """
    if not version:
        from udiskie.dbus import DBusException
        try:
            return get_backend(clsname, 2)
        except DBusException:
            log = logging.getLogger(__name__)
            log.warning(_('Failed to connect UDisks2 dbus service..\n'
                          'Falling back to UDisks1.'))
            return get_backend(clsname, 1)
    elif version == 1:
        import udiskie.udisks1
        return getattr(udiskie.udisks1, clsname)()
    elif version == 2:
        import udiskie.udisks2
        return getattr(udiskie.udisks2, clsname)()
    else:
        raise ValueError(_("UDisks version not supported: {0}!", version))
Esempio n. 3
0
    def device_unmounted(self, device):
        """
        Show 'Device unmounted' notification.

        :param device: device object
        """
        self._show_notification('device_unmounted', _('Device unmounted'),
                                _('{0.id_label} unmounted', device),
                                'drive-removable-media')
Esempio n. 4
0
    def device_unlocked(self, device):
        """
        Show 'Device unlocked' notification.

        :param device: device object
        """
        self._show_notification('device_unlocked', _('Device unlocked'),
                                _('{0.device_presentation} unlocked', device),
                                'drive-removable-media')
Esempio n. 5
0
    def device_unmounted(self, device):
        """
        Show 'Device unmounted' notification.

        :param device: device object
        """
        self._show_notification(
            'device_unmounted',
            _('Device unmounted'),
            _('{0.id_label} unmounted', device),
            'drive-removable-media')
Esempio n. 6
0
    def device_unlocked(self, device):
        """
        Show 'Device unlocked' notification.

        :param device: device object
        """
        self._show_notification(
            'device_unlocked',
            _('Device unlocked'),
            _('{0.device_presentation} unlocked', device),
            'drive-removable-media')
Esempio n. 7
0
    def device_mounted(self, device):
        """
        Show 'Device mounted' notification with 'Browse directory' button.

        :param device: device object
        """
        browse_action = ('browse', _('Browse directory'), self._mounter.browse,
                         device)
        self._show_notification(
            'device_mounted', _('Device mounted'),
            _('{0.id_label} mounted on {0.mount_paths[0]}', device),
            'drive-removable-media', self._mounter._browser and browse_action)
Esempio n. 8
0
    def device_added(self, device):
        """
        Show 'Device added' notification.

        :param device: device object
        """
        device_file = device.device_presentation
        if (device.is_drive or device.is_toplevel) and device_file:
            self._show_notification(
                'device_added', _('Device added'),
                _('device appeared on {0.device_presentation}', device),
                'drive-removable-media')
Esempio n. 9
0
    def device_removed(self, device):
        """
        Show 'Device removed' notification.

        :param device: device object
        """
        device_file = device.device_presentation
        if (device.is_drive or device.is_toplevel) and device_file:
            self._show_notification(
                'device_removed',
                _('Device removed'),
                _('device disappeared on {0.device_presentation}', device),
                'drive-removable-media')
Esempio n. 10
0
    def device_mounted(self, device):
        """
        Show 'Device mounted' notification with 'Browse directory' button.

        :param device: device object
        """
        browse_action = ('browse', _('Browse directory'),
                         self._mounter.browse, device)
        self._show_notification(
            'device_mounted',
            _('Device mounted'),
            _('{0.id_label} mounted on {0.mount_paths[0]}', device),
            'drive-removable-media',
            self._mounter._browser and browse_action)
Esempio n. 11
0
def get_password_gui(device):
    """Get the password to unlock a device from GUI."""
    text = _('Enter password for {0.device_presentation}: ', device)
    try:
        return password_dialog('udiskie', text)
    except RuntimeError:
        return None
Esempio n. 12
0
    def add(self, device, recursive=False):
        """
        Mount or unlock the device depending on its type.

        :param device: device object, block device path or mount path
        :param bool recursive: recursively mount and unlock child devices
        :returns: whether all attempted operations succeeded
        :rtype: bool
        """
        if device.is_filesystem:
            success = self.mount(device)
        elif device.is_crypto:
            success = self.unlock(device)
            if success and recursive:
                # TODO: update device
                success = self.add(device.luks_cleartext_holder,
                                   recursive=True)
        elif recursive and device.is_partition_table:
            success = True
            for dev in self.get_all_handleable():
                if dev.is_partition and dev.partition_slave == device:
                    success = self.add(dev, recursive=True) and success
        else:
            self._log.info(_('not adding {0}: unhandled device', device))
            return False
        return success
Esempio n. 13
0
    def _branchmenu(self, groups):
        """
        Create a menu from the given node.

        :param Branch groups: contains information about the menu
        :returns: a new menu object holding all groups of the node
        :rtype: Gtk.Menu
        """
        def make_action_callback(node):
            return lambda _: node.action()
        menu = Gtk.Menu()
        separate = False
        for group in groups:
            if len(group) > 0:
                if separate:
                    menu.append(Gtk.SeparatorMenuItem())
                separate = True
            for node in group:
                if isinstance(node, Action):
                    menu.append(self._menuitem(
                        node.label,
                        self._icons.get_icon(node.method, Gtk.IconSize.MENU),
                        make_action_callback(node)))
                elif isinstance(node, Branch):
                    menu.append(self._menuitem(
                        node.label,
                        icon=None,
                        onclick=self._branchmenu(node.groups)))
                else:
                    raise ValueError(_("Invalid node!"))
        return menu
Esempio n. 14
0
    def __init__(self, match, value):
        """
        Construct an instance.

        :param dict match: device attributes
        :param list value: value
        """
        self._log = logging.getLogger(__name__)
        self._match = match.copy()
        # the use of keys() makes deletion inside the loop safe:
        for k in self._match.keys():
            if k not in self.VALID_PARAMETERS:
                self._log.warn(_('Unknown matching attribute: {!r}', k))
                del self._match[k]
        self._value = value
        self._log.debug(_('{0} created', self))
Esempio n. 15
0
def get_password_gui(device):
    """Get the password to unlock a device from GUI."""
    text = _('Enter password for {0.device_presentation}: ', device)
    try:
        return password_dialog('udiskie', text)
    except RuntimeError:
        return None
Esempio n. 16
0
    def _branchmenu(self, groups):
        """
        Create a menu from the given node.

        :param Branch groups: contains information about the menu
        :returns: a new menu object holding all groups of the node
        :rtype: Gtk.Menu
        """
        menu = Gtk.Menu()
        separate = False
        for group in groups:
            if len(group) > 0:
                if separate:
                    menu.append(Gtk.SeparatorMenuItem())
                separate = True
            for node in group:
                if isinstance(node, Action):
                    menu.append(self._actionitem(
                        node.method,
                        feed=[node.label],
                        bind=[node.device]))
                elif isinstance(node, Branch):
                    menu.append(self._menuitem(
                        node.label,
                        icon=None,
                        onclick=self._branchmenu(node.groups)))
                else:
                    raise ValueError(_("Invalid node!"))
        return menu
Esempio n. 17
0
    def add(self, device, recursive=False):
        """
        Mount or unlock the device depending on its type.

        :param device: device object, block device path or mount path
        :param bool recursive: recursively mount and unlock child devices
        :returns: whether all attempted operations succeeded
        :rtype: bool
        """
        if device.is_filesystem:
            success = self.mount(device)
        elif device.is_crypto:
            success = self.unlock(device)
            if success and recursive:
                # TODO: update device
                success = self.add(device.luks_cleartext_holder,
                                   recursive=True)
        elif recursive and device.is_partition_table:
            success = True
            for dev in self.get_all_handleable():
                if dev.is_partition and dev.partition_slave == device:
                    success = self.add(dev, recursive=True) and success
        else:
            self._log.info(_('not adding {0}: unhandled device', device))
            return False
        return success
Esempio n. 18
0
def browser(browser_name='xdg-open'):

    """
    Create a browse-directory function.

    :param str browser_name: file manager program name
    :returns: one-parameter open function
    :rtype: callable
    """

    if not browser_name:
        return None
    executable = find_executable(browser_name)
    if executable is None:
        # Why not raise an exception? -I think it is more convenient (for
        # end users) to have a reasonable default, without enforcing it.
        logging.getLogger(__name__).warn(
            _("Can't find file browser: {0!r}. "
              "You may want to change the value for the '-b' option.",
              browser_name))
        return None

    def browse(path):
        return subprocess.Popen([executable, path])

    return browse
Esempio n. 19
0
    def __init__(self, match, value):
        """
        Construct an instance.

        :param dict match: device attributes
        :param list value: value
        """
        self._log = logging.getLogger(__name__)
        self._match = match.copy()
        # the use of keys() makes deletion inside the loop safe:
        for k in self._match.keys():
            if k not in self.VALID_PARAMETERS:
                self._log.warn(_('Unknown matching attribute: {!r}', k))
                del self._match[k]
        self._value = value
        self._log.debug(_('{0} created', self))
Esempio n. 20
0
def browser(browser_name='xdg-open'):
    """
    Create a browse-directory function.

    :param str browser_name: file manager program name
    :returns: one-parameter open function
    :rtype: callable
    """

    if not browser_name:
        return None
    executable = find_executable(browser_name)
    if executable is None:
        # Why not raise an exception? -I think it is more convenient (for
        # end users) to have a reasonable default, without enforcing it.
        logging.getLogger(__name__).warn(
            _(
                "Can't find file browser: {0!r}. "
                "You may want to change the value for the '-b' option.",
                browser_name))
        return None

    def browse(path):
        return subprocess.Popen([executable, path])

    return browse
Esempio n. 21
0
def get_password_tty(device):
    """Get the password to unlock a device from terminal."""
    text = _('Enter password for {0.device_presentation}: ', device)
    try:
        return getpass.getpass(text)
    except EOFError:
        print("")
        return None
Esempio n. 22
0
def get_password_tty(device):
    """Get the password to unlock a device from terminal."""
    text = _('Enter password for {0.device_presentation}: ', device)
    try:
        return getpass.getpass(text)
    except EOFError:
        print("")
        return None
Esempio n. 23
0
    def lock(self, device):
        """
        Lock device if unlocked.

        :param device: device object, block device path or mount path
        :returns: whether the device is locked
        :rtype: bool
        """
        if not self.is_handleable(device) or not device.is_crypto:
            self._log.warn(_('not locking {0}: unhandled device', device))
            return False
        if not device.is_unlocked:
            self._log.info(_('not locking {0}: not unlocked', device))
            return True
        self._log.debug(_('locking {0}', device))
        device.lock()
        self._log.info(_('locked {0}', device))
        return True
Esempio n. 24
0
    def unmount(self, device):
        """
        Unmount a Device if mounted.

        :param device: device object, block device path or mount path
        :returns: whether the device is unmounted
        :rtype: bool
        """
        if not self.is_handleable(device) or not device.is_filesystem:
            self._log.warn(_('not unmounting {0}: unhandled device', device))
            return False
        if not device.is_mounted:
            self._log.info(_('not unmounting {0}: not mounted', device))
            return True
        self._log.debug(_('unmounting {0}', device))
        device.unmount()
        self._log.info(_('unmounted {0}', device))
        return True
Esempio n. 25
0
    def unmount(self, device):
        """
        Unmount a Device if mounted.

        :param device: device object, block device path or mount path
        :returns: whether the device is unmounted
        :rtype: bool
        """
        if not self.is_handleable(device) or not device.is_filesystem:
            self._log.warn(_('not unmounting {0}: unhandled device', device))
            return False
        if not device.is_mounted:
            self._log.info(_('not unmounting {0}: not mounted', device))
            return True
        self._log.debug(_('unmounting {0}', device))
        device.unmount()
        self._log.info(_('unmounted {0}', device))
        return True
Esempio n. 26
0
    def browse(self, device):
        """
        Browse device.

        :param device: device object, block device path or mount path
        :returns: success
        :rtype: bool
        """
        if not device.is_mounted:
            self._log.error(_("not browsing {0}: not mounted", device))
            return False
        if not self._browser:
            self._log.error(_("not browsing {0}: no program", device))
            return False
        self._log.debug(_('opening {0} on {0.mount_paths[0]}', device))
        self._browser(device.mount_paths[0])
        self._log.info(_('opened {0} on {0.mount_paths[0]}', device))
        return True
Esempio n. 27
0
    def browse(self, device):
        """
        Browse device.

        :param device: device object, block device path or mount path
        :returns: success
        :rtype: bool
        """
        if not device.is_mounted:
            self._log.error(_("not browsing {0}: not mounted", device))
            return False
        if not self._browser:
            self._log.error(_("not browsing {0}: no program", device))
            return False
        self._log.debug(_('opening {0} on {0.mount_paths[0]}', device))
        self._browser(device.mount_paths[0])
        self._log.info(_('opened {0} on {0.mount_paths[0]}', device))
        return True
Esempio n. 28
0
    def lock(self, device):
        """
        Lock device if unlocked.

        :param device: device object, block device path or mount path
        :returns: whether the device is locked
        :rtype: bool
        """
        if not self.is_handleable(device) or not device.is_crypto:
            self._log.warn(_('not locking {0}: unhandled device', device))
            return False
        if not device.is_unlocked:
            self._log.info(_('not locking {0}: not unlocked', device))
            return True
        self._log.debug(_('locking {0}', device))
        device.lock()
        self._log.info(_('locked {0}', device))
        return True
Esempio n. 29
0
def require_Gtk():
    """
    Make sure Gtk is properly initialized.

    :raises RuntimeError: if Gtk can not be properly initialized
    """
    # if we attempt to create any GUI elements with no X server running the
    # program will just crash, so let's make a way to catch this case:
    if not Gtk.init_check(None)[0]:
        raise RuntimeError(_("X server not connected!"))
Esempio n. 30
0
    def job_failed(self, device, action, message):
        """
        Show 'Job failed' notification with 'Retry' button.

        :param device: device object
        """
        device_file = device.device_presentation or device.object_path
        if message:
            text = _('failed to {0} {1}:\n{2}', action, device_file, message)
        else:
            text = _('failed to {0} device {1}.', action, device_file)
        try:
            retry = getattr(self._mounter, action)
        except AttributeError:
            retry_action = None
        else:
            retry_action = ('retry', _('Retry'), retry, device)
        self._show_notification('job_failed', _('Job failed'), text,
                                'drive-removable-media', retry_action)
Esempio n. 31
0
 def wrapper(self, device_or_path, *args, **kwargs):
     if isinstance(device_or_path, basestring):
         device = self.udisks.find(device_or_path)
         if device:
             self._log.debug(_('found device owning "{0}": "{1}"',
                             device_or_path, device))
         else:
             self._log.error(_('no device found owning "{0}"', device_or_path))
             return False
     else:
         device = device_or_path
     try:
         return fn(self, device, *args, **kwargs)
     except device.Exception:
         err = sys.exc_info()[1]
         self._log.error(_('failed to {0} {1}: {2}',
                         fn.__name__, device, err.message))
         self._set_error(device, fn.__name__, err.message)
         return False
Esempio n. 32
0
def require_Gtk():
    """
    Make sure Gtk is properly initialized.

    :raises RuntimeError: if Gtk can not be properly initialized
    """
    # if we attempt to create any GUI elements with no X server running the
    # program will just crash, so let's make a way to catch this case:
    if not Gtk.init_check(None)[0]:
        raise RuntimeError(_("X server not connected!"))
Esempio n. 33
0
    def value(self, device):
        """
        Get the associated value.

        :param Device device: matched device

        If :meth:`match` is False for the device, the return value of this
        method is undefined.
        """
        self._log.debug(_('{0} used for {1}', self, device.object_path))
        return self._value
Esempio n. 34
0
    def value(self, device):
        """
        Get the associated value.

        :param Device device: matched device

        If :meth:`match` is False for the device, the return value of this
        method is undefined.
        """
        self._log.debug(_('{0} used for {1}', self, device.object_path))
        return self._value
Esempio n. 35
0
 def wrapper(self, device_or_path, *args, **kwargs):
     if isinstance(device_or_path, basestring):
         device = self.udisks.find(device_or_path)
         if device:
             self._log.debug(
                 _('found device owning "{0}": "{1}"', device_or_path,
                   device))
         else:
             self._log.error(
                 _('no device found owning "{0}"', device_or_path))
             return False
     else:
         device = device_or_path
     try:
         return fn(self, device, *args, **kwargs)
     except device.Exception:
         err = sys.exc_info()[1]
         self._log.error(
             _('failed to {0} {1}: {2}', fn.__name__, device, err.message))
         self._set_error(device, fn.__name__, err.message)
         return False
Esempio n. 36
0
    def mount(self, device):
        """
        Mount the device if not already mounted.

        :param device: device object, block device path or mount path
        :returns: whether the device is mounted.
        :rtype: bool
        """
        if not self.is_handleable(device) or not device.is_filesystem:
            self._log.warn(_('not mounting {0}: unhandled device', device))
            return False
        if device.is_mounted:
            self._log.info(_('not mounting {0}: already mounted', device))
            return True
        fstype = str(device.id_type)
        options = self._mount_options(device)
        kwargs = dict(fstype=fstype, options=options)
        self._log.debug(_('mounting {0} with {1}', device, kwargs))
        mount_path = device.mount(**kwargs)
        self._log.info(_('mounted {0} on {1}', device, mount_path))
        return True
Esempio n. 37
0
    def mount(self, device):
        """
        Mount the device if not already mounted.

        :param device: device object, block device path or mount path
        :returns: whether the device is mounted.
        :rtype: bool
        """
        if not self.is_handleable(device) or not device.is_filesystem:
            self._log.warn(_('not mounting {0}: unhandled device', device))
            return False
        if device.is_mounted:
            self._log.info(_('not mounting {0}: already mounted', device))
            return True
        fstype = str(device.id_type)
        options = self._mount_options(device)
        kwargs = dict(fstype=fstype, options=options)
        self._log.debug(_('mounting {0} with {1}', device, kwargs))
        mount_path = device.mount(**kwargs)
        self._log.info(_('mounted {0} on {1}', device, mount_path))
        return True
Esempio n. 38
0
    def unlock(self, device):
        """
        Unlock the device if not already unlocked.

        :param device: device object, block device path or mount path
        :returns: whether the device is unlocked
        :rtype: bool
        """
        if not self.is_handleable(device) or not device.is_crypto:
            self._log.warn(_('not unlocking {0}: unhandled device', device))
            return False
        if device.is_unlocked:
            self._log.info(_('not unlocking {0}: already unlocked', device))
            return True
        if not self._prompt:
            self._log.error(_('not unlocking {0}: no password prompt', device))
            return False
        password = self._prompt(device)
        if password is None:
            self._log.debug(_('not unlocking {0}: cancelled by user', device))
            return False
        self._log.debug(_('unlocking {0}', device))
        device.unlock(password)
        self._log.info(_('unlocked {0}', device))
        return True
Esempio n. 39
0
    def unlock(self, device):
        """
        Unlock the device if not already unlocked.

        :param device: device object, block device path or mount path
        :returns: whether the device is unlocked
        :rtype: bool
        """
        if not self.is_handleable(device) or not device.is_crypto:
            self._log.warn(_('not unlocking {0}: unhandled device', device))
            return False
        if device.is_unlocked:
            self._log.info(_('not unlocking {0}: already unlocked', device))
            return True
        if not self._prompt:
            self._log.error(_('not unlocking {0}: no password prompt', device))
            return False
        password = self._prompt(device)
        if password is None:
            self._log.debug(_('not unlocking {0}: cancelled by user', device))
            return False
        self._log.debug(_('unlocking {0}', device))
        device.unlock(password)
        self._log.info(_('unlocked {0}', device))
        return True
Esempio n. 40
0
    def find(self, path):
        """
        Get a device proxy by device name or any mount path of the device.

        This searches through all accessible devices and compares device
        path as well as mount pathes.
        """
        for device in self:
            if device.is_file(path):
                return device
        logger = logging.getLogger(__name__)
        logger.warn(_('Device not found: {0}', path))
        return None
Esempio n. 41
0
    def detach(self, device, force=False):
        """
        Detach a device after unmounting all its mounted filesystems.

        :param device: device object, block device path or mount path
        :param bool force: remove child devices before trying to detach
        :returns: whether the operation succeeded
        :rtype: bool
        """
        if not self.is_handleable(device):
            self._log.warn(_('not detaching {0}: unhandled device'))
            return False
        drive = device.root
        if not drive.is_detachable:
            self._log.warn(_('not detaching {0}: drive not detachable', drive))
            return False
        if force:
            self.remove(drive, force=True)
        self._log.debug(_('detaching {0}', device))
        drive.detach()
        self._log.info(_('detached {0}', device))
        return True
Esempio n. 42
0
    def detach(self, device, force=False):
        """
        Detach a device after unmounting all its mounted filesystems.

        :param device: device object, block device path or mount path
        :param bool force: remove child devices before trying to detach
        :returns: whether the operation succeeded
        :rtype: bool
        """
        if not self.is_handleable(device):
            self._log.warn(_('not detaching {0}: unhandled device'))
            return False
        drive = device.root
        if not drive.is_detachable:
            self._log.warn(_('not detaching {0}: drive not detachable', drive))
            return False
        if force:
            self.remove(drive, force=True)
        self._log.debug(_('detaching {0}', device))
        drive.detach()
        self._log.info(_('detached {0}', device))
        return True
Esempio n. 43
0
    def job_failed(self, device, action, message):
        """
        Show 'Job failed' notification with 'Retry' button.

        :param device: device object
        """
        device_file = device.device_presentation or device.object_path
        if message:
            text = _('failed to {0} {1}:\n{2}', action, device_file, message)
        else:
            text = _('failed to {0} device {1}.', action, device_file)
        try:
            retry = getattr(self._mounter, action)
        except AttributeError:
            retry_action = None
        else:
            retry_action = ('retry', _('Retry'), retry, device)
        self._show_notification(
            'job_failed',
            _('Job failed'), text,
            'drive-removable-media',
            retry_action)
Esempio n. 44
0
    def find(self, path):
        """
        Get a device proxy by device name or any mount path of the device.

        This searches through all accessible devices and compares device
        path as well as mount pathes.
        """
        for device in self:
            if device.is_file(path):
                return device
        logger = logging.getLogger(__name__)
        logger.warn(_('Device not found: {0}', path))
        return None
Esempio n. 45
0
    def remove(self,
               device,
               force=False,
               detach=False,
               eject=False,
               lock=False):
        """
        Unmount or lock the device depending on device type.

        :param device: device object, block device path or mount path
        :param bool force: recursively remove all child devices
        :param bool detach: detach the root drive
        :param bool eject: remove media from the root drive
        :param bool lock: lock the associated LUKS cleartext slave
        :returns: whether all attempted operations succeeded
        :rtype: bool
        """
        if device.is_filesystem:
            success = self.unmount(device)
        elif device.is_crypto:
            if force and device.is_unlocked:
                self.remove(device.luks_cleartext_holder, force=True)
            success = self.lock(device)
        elif force and (device.is_partition_table or device.is_drive):
            success = True
            for child in self.get_all_handleable():
                if _is_parent_of(device, child):
                    success = self.remove(child,
                                          force=True,
                                          detach=detach,
                                          eject=eject,
                                          lock=lock) and success
        else:
            self._log.info(_('not removing {0}: unhandled device', device))
            success = False
        # if these operations work, everything is fine, we can return True:
        if lock and device.is_luks_cleartext:
            device = device.luks_cleartext_slave
            success = self.lock(device)
        if eject:
            success = self.eject(device)
        if detach:
            success = self.detach(device)
        return success
Esempio n. 46
0
    def remove(self, device, force=False, detach=False, eject=False,
               lock=False):
        """
        Unmount or lock the device depending on device type.

        :param device: device object, block device path or mount path
        :param bool force: recursively remove all child devices
        :param bool detach: detach the root drive
        :param bool eject: remove media from the root drive
        :param bool lock: lock the associated LUKS cleartext slave
        :returns: whether all attempted operations succeeded
        :rtype: bool
        """
        if device.is_filesystem:
            success = self.unmount(device)
        elif device.is_crypto:
            if force and device.is_unlocked:
                self.remove(device.luks_cleartext_holder, force=True)
            success = self.lock(device)
        elif force and (device.is_partition_table or device.is_drive):
            success = True
            for child in self.get_all_handleable():
                if _is_parent_of(device, child):
                    success = self.remove(child,
                                          force=True,
                                          detach=detach,
                                          eject=eject,
                                          lock=lock) and success
        else:
            self._log.info(_('not removing {0}: unhandled device', device))
            success = False
        # if these operations work, everything is fine, we can return True:
        if lock and device.is_luks_cleartext:
            device = device.luks_cleartext_slave
            success = self.lock(device)
        if eject:
            success = self.eject(device)
        if detach:
            success = self.detach(device)
        return success
Esempio n. 47
0
    def _device_job_changed(self,
                            object_path,
                            job_in_progress,
                            job_id,
                            job_initiated_by_user,
                            job_is_cancellable,
                            job_percentage):
        """
        Detect type of event and trigger appropriate event handlers.

        Internal method.
        """
        try:
            if job_id:
                action = self._action_mapping[job_id]
            else:
                action = self._jobs[object_path]
        except KeyError:
            # this can happen
            # a) at startup, when we only see the completion of a job
            # b) when we get notified about a job, which we don't handle
            return
        # NOTE: The here used heuristic is prone to raise conditions.
        if job_in_progress:
            # Cache the action name for later use:
            self._jobs[object_path] = action
        else:
            del self._jobs[object_path]
            device = self[object_path]
            if self._check_success[action](device):
                event = self._event_mapping[action]
                self.trigger(event, device)
            else:
                # get and delete message, if available:
                message = self._errors[action].pop(object_path, "")
                self.trigger('job_failed', device, action, message)
                log = logging.getLogger(__name__)
                log.info(_('{0} operation failed for device: {1}',
                           job_id, object_path))
Esempio n. 48
0
    def _device_job_changed(self, object_path, job_in_progress, job_id,
                            job_initiated_by_user, job_is_cancellable,
                            job_percentage):
        """
        Detect type of event and trigger appropriate event handlers.

        Internal method.
        """
        try:
            if job_id:
                action = self._action_mapping[job_id]
            else:
                action = self._jobs[object_path]
        except KeyError:
            # this can happen
            # a) at startup, when we only see the completion of a job
            # b) when we get notified about a job, which we don't handle
            return
        # NOTE: The here used heuristic is prone to raise conditions.
        if job_in_progress:
            # Cache the action name for later use:
            self._jobs[object_path] = action
        else:
            del self._jobs[object_path]
            device = self[object_path]
            if self._check_success[action](device):
                event = self._event_mapping[action]
                self.trigger(event, device)
            else:
                # get and delete message, if available:
                message = self._errors[action].pop(object_path, "")
                self.trigger('job_failed', device, action, message)
                log = logging.getLogger(__name__)
                log.info(
                    _('{0} operation failed for device: {1}', job_id,
                      object_path))
Esempio n. 49
0
 def _check(self, args):
     """Exit in case of multiple exclusive arguments."""
     if sum(bool(args[arg]) for arg in self._mapping) > 1:
         raise DocoptExit(_('These options are mutually exclusive: {0}',
                            ', '.join(self._mapping)))
Esempio n. 50
0
 def trigger(self, event, device, *args):
     self._log.debug(_("+++ {0}: {1}", event, device))
     super(Daemon, self).trigger(event, device, *args)
Esempio n. 51
0
class _EntryPoint(object):

    """
    Abstract base class for program entry points.

    Concrete implementations need to implement :meth:`run` and extend
    :meth:`finalize_options` to be usable with :meth:`main`. Furthermore
    the docstring of any concrete implementation must be usable with
    docopt. :ivar:`name` must be set to the name of the CLI utility.
    """

    option_defaults = {
        'log_level': logging.INFO,
        'udisks_version': None,
    }

    option_rules = {
        'log_level': Choice({
            '--verbose': logging.DEBUG,
            '--quiet': logging.ERROR}),
        'udisks_version': Choice({
            '--udisks-auto': 0,
            '--use-udisks1': 1,
            '--use-udisks2': 2}),
    }

    usage_remarks = _("""
    Note, that the options in the individual groups are mutually exclusive.

    The config file can be a JSON or preferrably a YAML file. For an
    example, see the MAN page (or doc/udiskie.8.txt in the repository).
    """)

    def __init__(self, argv=None):
        """
        Parse command line options, read config and initialize members.

        :param list argv: command line parameters
        """
        # parse program options (retrieve log level and config file name):
        args = docopt(self.usage, version=self.name + ' ' + self.version)
        default_opts = self.option_defaults
        program_opts = self.program_options(args)
        # initialize logging configuration:
        log_level = program_opts.get('log_level', default_opts['log_level'])
        if log_level <= logging.DEBUG:
            fmt = _('%(levelname)s [%(asctime)s] %(name)s: %(message)s')
        else:
            fmt = _('%(message)s')
        logging.basicConfig(level=log_level, format=fmt)
        # parse config options
        config_file = OptionalValue('--config')(args)
        config = udiskie.config.Config.from_file(config_file)
        options = {}
        options.update(default_opts)
        options.update(config.program_options)
        options.update(program_opts)
        # initialize instance variables
        self.config = config
        self.options = options
        self._init(config, options)

    def program_options(self, args):
        """
        Fully initialize Daemon object.

        :param dict args: arguments as parsed by docopt
        :returns: options from command line
        :rtype: dict
        """
        options = {}
        for name, rule in self.option_rules.items():
            val = rule(args)
            if val is not None:
                options[name] = val
        return options

    @classmethod
    def main(cls, argv=None):
        """
        Run program.

        :param list argv: command line parameters
        :returns: program exit code
        :rtype: int
        """
        return cls(argv).run()

    @property
    def version(self):
        """Get the version from setuptools metadata."""
        return udiskie.__version__

    @property
    def usage(self):
        """Get the full usage string."""
        return inspect.cleandoc(self.__doc__ + self.usage_remarks)

    @property
    def name(self):
        """Get the name of the CLI utility."""
        raise NotImplementedError()

    def _init(self, config, options):
        """
        Fully initialize Daemon object.

        :param Config config: configuration object
        :param options: program options
        """
        raise NotImplementedError()

    def run(self):
        """
        Run main program logic.

        :param options: program options
        :returns: exit code
        :rtype: int
        """
        raise NotImplementedError()
Esempio n. 52
0
 def __str__(self):
     return _('{0}(match={1!r}, value={2!r})',
              self.__class__.__name__,
              self._match,
              self._value)
Esempio n. 53
0
 def __str__(self):
     return _('{0}(match={1!r}, value={2!r})', self.__class__.__name__,
              self._match, self._value)
Esempio n. 54
0
 def _create_statusicon(self):
     """Return a new Gtk.StatusIcon."""
     statusicon = Gtk.StatusIcon()
     statusicon.set_from_gicon(self._icons.get_gicon('media'))
     statusicon.set_tooltip_text(_("udiskie"))
     return statusicon
Esempio n. 55
0
class UdiskieMenu(object):

    """
    Builder for udiskie menus.

    Objects of this class generate action menus when being called.
    """

    _menu_labels = {
        'browse': _('Browse {0}'),
        'mount': _('Mount {0}'),
        'unmount': _('Unmount {0}'),
        'unlock': _('Unlock {0}'),
        'lock': _('Lock {0}'),
        'eject': _('Eject {0}'),
        'detach': _('Unpower {0}'),
        'quit': _('Quit'), }

    def __init__(self, mounter, icons, actions={}):
        """
        Initialize a new menu maker.

        :param object mounter: mount operation provider
        :param Icons icons: icon provider
        :param dict actions: actions for menu items
        :returns: a new menu maker
        :rtype: cls

        Required keys for the ``_menu_labels``, ``_menu_icons`` and
        ``actions`` dictionaries are:

            - browse    Open mount location
            - mount     Mount a device
            - unmount   Unmount a device
            - unlock    Unlock a LUKS device
            - lock      Lock a LUKS device
            - eject     Eject a drive
            - detach    Detach (power down) a drive
            - quit      Exit the application

        NOTE: If using a main loop other than ``Gtk.main`` the 'quit' action
        must be customized.
        """
        self._icons = icons
        self._mounter = mounter
        _actions = actions.copy()
        setdefault(_actions, {
            'browse': mounter.browse,
            'mount': mounter.mount,
            'unmount': mounter.unmount,
            'unlock': mounter.unlock,
            'lock': partial(mounter.remove, force=True),
            'eject': partial(mounter.eject, force=True),
            'detach': partial(mounter.detach, force=True),
            'quit': Gtk.main_quit, })
        self._actions = _actions

    def __call__(self):
        """
        Create menu for udiskie mount operations.

        :returns: a new menu
        :rtype: Gtk.Menu
        """
        # create actions items
        menu = self._branchmenu(self._prepare_menu(self.detect()).groups)
        # append menu item for closing the application
        if self._actions.get('quit'):
            if len(menu) > 0:
                menu.append(Gtk.SeparatorMenuItem())
            menu.append(self._actionitem('quit'))
        return menu

    def detect(self):
        """
        Detect all currently known devices.

        :returns: root of device hierarchy
        :rtype: Node
        """
        root = Node(None, [], None, "", [])
        device_nodes = dict(map(self._device_node,
                                self._mounter.get_all_handleable()))
        # insert child devices as branches into their roots:
        for object_path, node in device_nodes.items():
            device_nodes.get(node.root, root).branches.append(node)
        return root

    def _branchmenu(self, groups):
        """
        Create a menu from the given node.

        :param Branch groups: contains information about the menu
        :returns: a new menu object holding all groups of the node
        :rtype: Gtk.Menu
        """
        menu = Gtk.Menu()
        separate = False
        for group in groups:
            if len(group) > 0:
                if separate:
                    menu.append(Gtk.SeparatorMenuItem())
                separate = True
            for node in group:
                if isinstance(node, Action):
                    menu.append(self._actionitem(
                        node.method,
                        feed=[node.label],
                        bind=[node.device]))
                elif isinstance(node, Branch):
                    menu.append(self._menuitem(
                        node.label,
                        icon=None,
                        onclick=self._branchmenu(node.groups)))
                else:
                    raise ValueError(_("Invalid node!"))
        return menu

    def _menuitem(self, label, icon, onclick):
        """
        Create a generic menu item.

        :param str label: text
        :param Gtk.Image icon: icon (may be ``None``)
        :param onclick: onclick handler, either a callable or Gtk.Menu
        :returns: the menu item object
        :rtype: Gtk.MenuItem
        """
        if icon is None:
            item = Gtk.MenuItem()
        else:
            item = Gtk.ImageMenuItem()
            item.set_image(icon)
            # I don't really care for the "show icons only for nouns, not
            # for verbs" policy:
            item.set_always_show_image(True)
        if label is not None:
            item.set_label(label)
        if isinstance(onclick, Gtk.Menu):
            item.set_submenu(onclick)
        else:
            item.connect('activate', onclick)
        return item

    def _actionitem(self, action, feed=(), bind=()):
        """
        Create a menu item for the specified action.

        :param str action: name of the action
        :param tuple feed: parameters for the label text
        :param tuple bind: parameters for the onclick handler
        :returns: the menu item object
        :rtype: Gtk.MenuItem
        """
        return self._menuitem(
            self._menu_labels[action].format(*feed),
            self._icons.get_icon(action, Gtk.IconSize.MENU),
            lambda _: self._actions[action](*bind))

    def _get_device_methods(self, device):
        """Return an iterable over all available methods the device has."""
        if device.is_filesystem:
            if device.is_mounted:
                yield 'browse'
                yield 'unmount'
            else:
                yield 'mount'
        elif device.is_crypto:
            if device.is_unlocked:
                yield 'lock'
            else:
                yield 'unlock'
        if device.is_ejectable and device.has_media:
            yield 'eject'
        if device.is_detachable:
            yield 'detach'

    def _device_node(self, device):
        """Create an empty menu node for the specified device."""
        label = device.id_label or device.device_presentation
        # determine available methods
        methods = [method
                   for method in self._get_device_methods(device)
                   if self._actions[method]]
        # find the root device:
        if device.is_partition:
            root = device.partition_slave.object_path
        elif device.is_luks_cleartext:
            root = device.luks_cleartext_slave.object_path
        else:
            root = None
        # in this first step leave branches empty
        return device.object_path, Node(root, [], device, label, methods)

    def _prepare_menu(self, node):
        """
        Prepare the menu hierarchy from the given device tree.

        :param Node node: root node of device hierarchy
        :returns: menu hierarchy
        :rtype: Branch
        """
        return Branch(
            label=node.label,
            groups=[
                [self._prepare_menu(branch)
                 for branch in node.branches],
                [Action(node.label, node.device, method)
                 for method in node.methods],
            ])