Enable detection of MTP devices in the GUI and with ebook-device, with a tweak. Note that MTP support is not yet completed.

This commit is contained in:
Kovid Goyal 2012-09-01 12:54:12 +05:30
parent 786729fa6f
commit 5601852363
4 changed files with 104 additions and 29 deletions

View File

@ -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 {{{

View File

@ -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

View File

@ -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,

View File

@ -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