From 54e510a41572273ebbd7a260c339e14f6054e94f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 27 Jun 2021 16:01:10 +0530 Subject: [PATCH] Port freebsd device open/eject code to use jeepney Note that I have no way to test this code --- src/calibre/devices/usbms/device.py | 158 +++++----------------------- src/calibre/devices/usbms/hal.py | 138 ++++++++++++++++++++++++ 2 files changed, 164 insertions(+), 132 deletions(-) create mode 100644 src/calibre/devices/usbms/hal.py diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index 2af3db7e1d..85389abff9 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -12,18 +12,22 @@ intended to be subclassed with the relevant parts implemented for a particular device. This class handles device detection. ''' -import os, subprocess, time, re, sys, glob -from itertools import repeat +import glob +import os +import re +import subprocess +import sys +import time from collections import namedtuple +from itertools import repeat from calibre import prints -from calibre.constants import DEBUG -from calibre.devices.interface import DevicePlugin +from calibre.constants import DEBUG, isfreebsd, islinux, ismacos, iswindows from calibre.devices.errors import DeviceError +from calibre.devices.interface import DevicePlugin from calibre.devices.usbms.deviceconfig import DeviceConfig -from calibre.constants import iswindows, islinux, ismacos, isfreebsd from calibre.utils.filenames import ascii_filename as sanitize -from polyglot.builtins import iteritems, string_or_bytes, map +from polyglot.builtins import iteritems, map, string_or_bytes if ismacos: osx_sanitize_name_pat = re.compile(r'[.-]') @@ -649,7 +653,6 @@ class Device(DeviceConfig, DevicePlugin): # 4. when finished, we have a list of mount points and associated dbus nodes # def open_freebsd(self): - import dbus # There should be some way to access the -v arg... verbose = False @@ -660,107 +663,17 @@ class Device(DeviceConfig, DevicePlugin): if not d.serial: raise DeviceError("Device has no S/N. Can't continue") return False - - vols=[] - - bus = dbus.SystemBus() - manager = dbus.Interface(bus.get_object('org.freedesktop.Hal', - '/org/freedesktop/Hal/Manager'), 'org.freedesktop.Hal.Manager') - paths = manager.FindDeviceStringMatch('usb.serial',d.serial) - for path in paths: - objif = dbus.Interface(bus.get_object('org.freedesktop.Hal', path), 'org.freedesktop.Hal.Device') - # Extra paranoia... - try: - if d.idVendor == objif.GetProperty('usb.vendor_id') and \ - d.idProduct == objif.GetProperty('usb.product_id') and \ - d.manufacturer == objif.GetProperty('usb.vendor') and \ - d.product == objif.GetProperty('usb.product') and \ - d.serial == objif.GetProperty('usb.serial'): - midpath = manager.FindDeviceStringMatch('info.parent', path) - dpaths = manager.FindDeviceStringMatch( - 'storage.originating_device', path) + manager.FindDeviceStringMatch('storage.originating_device', midpath[0]) - for dpath in dpaths: - # devif = dbus.Interface(bus.get_object('org.freedesktop.Hal', dpath), 'org.freedesktop.Hal.Device') - try: - vpaths = manager.FindDeviceStringMatch('block.storage_device', dpath) - for vpath in vpaths: - try: - vdevif = dbus.Interface(bus.get_object('org.freedesktop.Hal', vpath), 'org.freedesktop.Hal.Device') - if not vdevif.GetProperty('block.is_volume'): - continue - if vdevif.GetProperty('volume.fsusage') != 'filesystem': - continue - volif = dbus.Interface(bus.get_object('org.freedesktop.Hal', vpath), 'org.freedesktop.Hal.Device.Volume') - pdevif = dbus.Interface(bus.get_object('org.freedesktop.Hal', vdevif.GetProperty('info.parent')), - 'org.freedesktop.Hal.Device') - vol = {'node': pdevif.GetProperty('block.device'), - 'dev': vdevif, - 'vol': volif, - 'label': vdevif.GetProperty('volume.label')} - vols.append(vol) - except dbus.exceptions.DBusException as e: - print(e) - continue - except dbus.exceptions.DBusException as e: - print(e) - continue - except dbus.exceptions.DBusException: - continue - - vols.sort(key=lambda x: x['node']) - + from .hal import get_hal + hal = get_hal() + vols = hal.get_volumes(d) if verbose: print("FBSD: ", vols) - mtd=0 - - for vol in vols: - mp = '' - if vol['dev'].GetProperty('volume.is_mounted'): - mp = vol['dev'].GetProperty('volume.mount_point') - else: - try: - vol['vol'].Mount('Calibre-'+vol['label'], - vol['dev'].GetProperty('volume.fstype'), []) - loops = 0 - while not vol['dev'].GetProperty('volume.is_mounted'): - time.sleep(1) - loops += 1 - if loops > 100: - print("ERROR: Timeout waiting for mount to complete") - continue - mp = vol['dev'].GetProperty('volume.mount_point') - except dbus.exceptions.DBusException as e: - print("Failed to mount ", e) - continue - - # Mount Point becomes Mount Path - mp += '/' - - if verbose: - print("FBSD: mounted", vol['label'], "on", mp) - if mtd == 0: - self._main_prefix = mp - self._main_vol = vol['vol'] - if verbose: - print("FBSD: main = ", self._main_prefix) - if mtd == 1: - self._card_a_prefix = mp - self._card_a_vol = vol['vol'] - if verbose: - print("FBSD: card a = ", self._card_a_prefix) - if mtd == 2: - self._card_b_prefix = mp - self._card_b_vol = vol['vol'] - if verbose: - print("FBSD: card b = ", self._card_b_prefix) - # Note that mtd is used as a bool... not incrementing is fine. - break - mtd += 1 - - if mtd > 0: - return True - raise DeviceError(_('Unable to mount the device')) + ok, mv = hal.mount_volumes(vols) + if not ok: + raise DeviceError(_('Unable to mount the device')) + for k, v in mv.items(): + setattr(self, k, v) # # ------------------------------------------------------ @@ -770,37 +683,18 @@ class Device(DeviceConfig, DevicePlugin): # mounted filesystems, using the stored volume object # def eject_freebsd(self): - import dbus - # There should be some way to access the -v arg... - verbose = False - + from .hal import get_hal + hal = get_hal() if self._main_prefix: - if verbose: - print("FBSD: umount main:", self._main_prefix) - try: - self._main_vol.Unmount([]) - except dbus.exceptions.DBusException as e: - print('Unable to eject ', e) - + hal.unmount(self._main_vol) if self._card_a_prefix: - if verbose: - print("FBSD: umount card a:", self._card_a_prefix) - try: - self._card_a_vol.Unmount([]) - except dbus.exceptions.DBusException as e: - print('Unable to eject ', e) - + hal.unmount(self._card_a_vol) if self._card_b_prefix: - if verbose: - print("FBSD: umount card b:", self._card_b_prefix) - try: - self._card_b_vol.Unmount([]) - except dbus.exceptions.DBusException as e: - print('Unable to eject ', e) + hal.unmount(self._card_b_vol) - self._main_prefix = None - self._card_a_prefix = None - self._card_b_prefix = None + self._main_prefix = self._main_vol = None + self._card_a_prefix = self._card_a_vol = None + self._card_b_prefix = self._card_b_vol = None # ------------------------------------------------------ def open(self, connected_device, library_uuid): diff --git a/src/calibre/devices/usbms/hal.py b/src/calibre/devices/usbms/hal.py new file mode 100644 index 0000000000..68cd4204b3 --- /dev/null +++ b/src/calibre/devices/usbms/hal.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +# License: GPL v3 Copyright: 2021, Kovid Goyal + +import time +from jeepney import ( + DBusAddress, DBusErrorResponse, MessageType, Properties, new_method_call +) +from jeepney.io.blocking import open_dbus_connection +from calibre.constants import DEBUG + + +class HAL: + + def __init__(self): + self.bus = open_dbus_connection('SYSTEM') + + def send(self, msg): + reply = self.bus.send_and_get_reply(msg) + if reply.header.message_type is MessageType.error: + raise DBusErrorResponse(reply) + return reply.body[0] + + def call(self, addr, method, sig='', *args): + if sig: + return self.send(new_method_call(addr, method, sig, args)) + return self.send(new_method_call(addr, method)) + + def prop(self, addr, name): + return self.send(Properties(addr).get(name)) + + def addr(self, path, interface): + return DBusAddress(path, bus_name='org.freedesktop.Hal', interface=f'org.freedesktop.Hal.{interface}') + + def get_volume(self, vpath): + vdevif = self.addr(vpath, 'Device') + if not self.prop(vdevif, 'block.is_volume') or self.prop(vdevif, 'volume.fsusage') != 'filesystem': + return + volif = self.addr(vpath, 'Volume') + pdevif = self.addr(self.prop(volif, 'info.parent'), 'Device') + return {'node': self.prop(pdevif, 'block.device'), + 'dev': vdevif, + 'vol': volif, + 'label': self.prop(vdevif, 'volume.label')} + + def get_volumes(self, d): + vols = [] + manager = self.addr('/org/freedesktop/Hal/Manager', 'Manager') + paths = self.call(manager, 'FindDeviceStringMatch', 'ss', 'usb.serial', d.serial) + for path in paths: + objif = self.addr(path, 'Device') + + # Extra paranoia... + try: + if d.idVendor == self.prop(objif, 'usb.vendor_id') and \ + d.idProduct == self.prop(objif, 'usb.product_id') and \ + d.manufacturer == self.prop(objif, 'usb.vendor') and \ + d.product == self.prop(objif, 'usb.product') and \ + d.serial == self.prop(objif, 'usb.serial'): + midpath = self.call(manager, 'FindDeviceStringMatch', 'ss', 'info.parent', path) + dpaths = self.call(manager, 'FindDeviceStringMatch', 'ss', 'storage.originating_device', path + ) + self.call(manager, 'FindDeviceStringMatch', 'ss', 'storage.originating_device', midpath[0]) + for dpath in dpaths: + try: + vpaths = self.call(manager, 'FindDeviceStringMatch', 'block.storage_device', dpath) + for vpath in vpaths: + try: + vol = self.get_volume(vpath) + if vol is not None: + vols.append(vol) + except DBusErrorResponse as e: + print(e) + continue + except DBusErrorResponse as e: + print(e) + continue + except DBusErrorResponse: + continue + vols.sort(key=lambda x: x['node']) + return vols + + def get_mount_point(self, vol): + if not self.prop(vol['dev'], 'volume.is_mounted'): + fstype = self.prop(vol['dev'], 'volume.fstype') + self.call(vol['vol'], 'Mount', 'ssas', 'Calibre-'+vol['label'], fstype, []) + loops = 0 + while not self.prop(vol['dev'], 'volume.is_mounted'): + time.sleep(1) + loops += 1 + if loops > 100: + raise Exception("ERROR: Timeout waiting for mount to complete") + return self.prop(vol['dev'], 'volume.mount_point') + + def mount_volumes(self, volumes): + mtd=0 + ans = { + '_main_prefix': None, '_main_vol': None, + '_card_a_prefix': None, '_card_a_vol': None, + '_card_b_prefix': None, '_card_b_vol': None, + } + for vol in volumes: + try: + mp = self.get_mount_point(vol) + except Exception as e: + print("Failed to mount: {vol['label']}", e) + continue + # Mount Point becomes Mount Path + mp += '/' + if DEBUG: + print("FBSD: mounted", vol['label'], "on", mp) + if mtd == 0: + ans['_main_prefix'], ans['_main_vol'] = mp, vol['vol'] + if DEBUG: + print("FBSD: main = ", mp) + elif mtd == 1: + ans['_card_a_prefix'], ans['_card_a_vol'] = mp, vol['vol'] + if DEBUG: + print("FBSD: card a = ", mp) + elif mtd == 2: + ans['_card_b_prefix'], ans['_card_b_vol'] = mp, vol['vol'] + if DEBUG: + print("FBSD: card b = ", mp) + break + mtd += 1 + + return mtd > 0, ans + + def unmount(self, vol): + try: + self.call(vol, 'Unmount', 'as', []) + except DBusErrorResponse as e: + print('Unable to eject ', e) + + +def get_hal(): + if not hasattr(get_hal, 'ans'): + get_hal.ans = HAL() + return get_hal.ans