From 5601852363ede69773d1ea3bbe39c11a9c5c5537 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 1 Sep 2012 12:54:12 +0530 Subject: [PATCH] Enable detection of MTP devices in the GUI and with ebook-device, with a tweak. Note that MTP support is not yet completed. --- src/calibre/customize/builtins.py | 7 +++- src/calibre/devices/cli.py | 20 +++++----- src/calibre/devices/interface.py | 41 +++++++++++++++++++ src/calibre/gui2/device.py | 65 +++++++++++++++++++++++-------- 4 files changed, 104 insertions(+), 29 deletions(-) diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 469195627c..c7dc6a5b95 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -675,7 +675,6 @@ from calibre.devices.bambook.driver import BAMBOOK from calibre.devices.boeye.driver import BOEYE_BEX, BOEYE_BDX from calibre.devices.smart_device_app.driver import SMART_DEVICE_APP - # Order here matters. The first matched device is the one used. plugins += [ HANLINV3, @@ -749,6 +748,12 @@ plugins += [ SMART_DEVICE_APP, USER_DEFINED, ] + +from calibre.utils.config_base import tweaks +if tweaks.get('test_mtp_driver', False): + from calibre.devices.mtp.driver import MTP_DEVICE + plugins.append(MTP_DEVICE) + # }}} # New metadata download plugins {{{ diff --git a/src/calibre/devices/cli.py b/src/calibre/devices/cli.py index 95181bf639..c7b105998d 100755 --- a/src/calibre/devices/cli.py +++ b/src/calibre/devices/cli.py @@ -9,7 +9,7 @@ For usage information run the script. import StringIO, sys, time, os from optparse import OptionParser -from calibre import __version__, __appname__ +from calibre import __version__, __appname__, human_readable from calibre.devices.errors import PathError from calibre.utils.terminfo import TerminalController from calibre.devices.errors import ArgumentError, DeviceError, DeviceLocked @@ -18,16 +18,6 @@ from calibre.devices.scanner import DeviceScanner MINIMUM_COL_WIDTH = 12 #: Minimum width of columns in ls output -def human_readable(size): - """ Convert a size in bytes into a human readle form """ - if size < 1024: divisor, suffix = 1, "" - elif size < 1024*1024: divisor, suffix = 1024., "K" - elif size < 1024*1024*1024: divisor, suffix = 1024*1024, "M" - elif size < 1024*1024*1024*1024: divisor, suffix = 1024*1024, "G" - size = str(size/divisor) - if size.find(".") > -1: size = size[:size.find(".")+2] - return size + suffix - class FileFormatter(object): def __init__(self, file, term): self.term = term @@ -207,11 +197,19 @@ def main(): scanner = DeviceScanner() scanner.scan() connected_devices = [] + for d in device_plugins(): try: d.startup() except: print ('Startup failed for device plugin: %s'%d) + if d.MANAGES_DEVICE_PRESENCE: + cd = d.detect_managed_devices(scanner.devices) + if cd is not None: + connected_devices.append((cd, d)) + dev = d + break + continue ok, det = scanner.is_device_connected(d) if ok: dev = d diff --git a/src/calibre/devices/interface.py b/src/calibre/devices/interface.py index 2c5f60ecfe..6d859c8f89 100644 --- a/src/calibre/devices/interface.py +++ b/src/calibre/devices/interface.py @@ -81,6 +81,19 @@ class DevicePlugin(Plugin): #: by. NUKE_COMMENTS = None + #: If True indicates that this driver completely manages device detection, + #: ejecting and so forth. If you set this to True, you *must* implement the + #: detect_managed_devices and debug_managed_device_detection methods. + #: A driver with this set to true is responsible for detection of devices, + #: managing a blacklist of devices, a list of ejected devices and so forth. + #: calibre will periodically call the detect_managed_devices() method and + #: is it returns a detected device, calibre will call open(). open() will + #: be called every time a device is returned even is previous calls to open() + #: failed, therefore the driver must maintain its own blacklist of failed + #: devices. Similarly, when ejecting, calibre will call eject() and then + #: assuming the next call to detect_managed_devices() returns None, it will + #: call post_yank_cleanup(). + MANAGES_DEVICE_PRESENCE = False @classmethod def get_gui_name(cls): @@ -196,6 +209,34 @@ class DevicePlugin(Plugin): return True, dev return False, None + def detect_managed_devices(self, devices_on_system, force_refresh=False): + ''' + Called only if MANAGES_DEVICE_PRESENCE is True. + + Scan for devices that this driver can handle. Should return a device + object if a device is found. This object will be passed to the open() + method as the connected_device. If no device is found, return None. + + This method is called periodically by the GUI, so make sure it is not + too resource intensive. Use a cache to avoid repeatedly scanning the + system. + + :param devices_on_system: Set of USB devices found on the system. + + :param force_refresh: If True and the driver uses a cache to prevent + repeated scanning, the cache must be flushed. + ''' + raise NotImplementedError() + + def debug_managed_device_detection(self, devices_on_system, output): + ''' + Called only if MANAGES_DEVICE_PRESENCE is True. + + Should write information about the devices detected on the system to + output, which is a file like object. + ''' + raise NotImplementedError() + # }}} def reset(self, key='-1', log_packets=False, report_progress=None, diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index 03e1932035..d5879042b4 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -128,6 +128,10 @@ class DeviceManager(Thread): # {{{ self.setDaemon(True) # [Device driver, Showing in GUI, Ejected] self.devices = list(device_plugins()) + self.managed_devices = [x for x in self.devices if + not x.MANAGES_DEVICE_PRESENCE] + self.unmanaged_devices = [x for x in self.devices if + x.MANAGES_DEVICE_PRESENCE] self.sleep_time = sleep_time self.connected_slot = connected_slot self.jobs = Queue.Queue(0) @@ -182,12 +186,15 @@ class DeviceManager(Thread): # {{{ prints('Unable to open device', str(dev)) prints(tb) continue - self.connected_device = dev - self.connected_device_kind = device_kind - self.connected_slot(True, device_kind) + self.after_device_connect(dev, device_kind) return True return False + def after_device_connect(self, dev, device_kind): + self.connected_device = dev + self.connected_device_kind = device_kind + self.connected_slot(True, device_kind) + def connected_device_removed(self): while True: try: @@ -215,22 +222,45 @@ class DeviceManager(Thread): # {{{ def detect_device(self): self.scanner.scan() + if self.is_device_connected: - connected, detected_device = \ - self.scanner.is_device_connected(self.connected_device, - only_presence=True) - if not connected: - if DEBUG: - # Allow the device subsystem to output debugging info about - # why it thinks the device is not connected. Used, for e.g. - # in the can_handle() method of the T1 driver + if self.connected_device.MANAGES_DEVICE_PRESENCE: + cd = self.connected_device.detect_managed_devices(self.scanner.devices) + if cd is None: + self.connected_device_removed() + else: + connected, detected_device = \ self.scanner.is_device_connected(self.connected_device, - only_presence=True, debug=True) - self.connected_device_removed() + only_presence=True) + if not connected: + if DEBUG: + # Allow the device subsystem to output debugging info about + # why it thinks the device is not connected. Used, for e.g. + # in the can_handle() method of the T1 driver + self.scanner.is_device_connected(self.connected_device, + only_presence=True, debug=True) + self.connected_device_removed() else: + for dev in self.unmanaged_devices: + try: + cd = dev.detect_managed_devices(self.scanner.devices) + except: + prints('Error during device detection for %s:'%dev) + traceback.print_exc() + else: + if cd is not None: + try: + dev.open(cd, self.current_library_uuid) + except: + prints('Error while trying to open %s (Driver: %s)'% + (cd, dev)) + traceback.print_exc() + else: + self.after_device_connect(dev, 'unmanaged-device') + return try: possibly_connected_devices = [] - for device in self.devices: + for device in self.managed_devices: if device in self.ejected_devices: continue try: @@ -248,7 +278,7 @@ class DeviceManager(Thread): # {{{ prints('Connect to device failed, retrying in 5 seconds...') time.sleep(5) if not self.do_connect(possibly_connected_devices, - device_kind='usb'): + device_kind='device'): if DEBUG: prints('Device connect failed again, giving up') except OpenFailed as e: @@ -264,9 +294,10 @@ class DeviceManager(Thread): # {{{ # disconnect a device def umount_device(self, *args): if self.is_device_connected and not self.job_manager.has_device_jobs(): - if self.connected_device_kind == 'device': + if self.connected_device_kind in {'unmanaged-device', 'device'}: self.connected_device.eject() - self.ejected_devices.add(self.connected_device) + if self.connected_device_kind != 'unmanaged-device': + self.ejected_devices.add(self.connected_device) self.connected_slot(False, self.connected_device_kind) elif hasattr(self.connected_device, 'unmount_device'): # As we are on the wrong thread, this call must *not* do