mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Refactor unix MTP driver to manage device presence
This commit is contained in:
parent
5cbed9ff4c
commit
5ac5928486
@ -26,11 +26,6 @@ class MTPDeviceBase(DevicePlugin):
|
|||||||
author = 'Kovid Goyal'
|
author = 'Kovid Goyal'
|
||||||
version = (1, 0, 0)
|
version = (1, 0, 0)
|
||||||
|
|
||||||
# Invalid USB vendor information so the scanner will never match
|
|
||||||
VENDOR_ID = [0xffff]
|
|
||||||
PRODUCT_ID = [0xffff]
|
|
||||||
BCD = [0xffff]
|
|
||||||
|
|
||||||
THUMBNAIL_HEIGHT = 128
|
THUMBNAIL_HEIGHT = 128
|
||||||
CAN_SET_METADATA = []
|
CAN_SET_METADATA = []
|
||||||
|
|
||||||
@ -51,4 +46,10 @@ class MTPDeviceBase(DevicePlugin):
|
|||||||
def get_gui_name(self):
|
def get_gui_name(self):
|
||||||
return self.current_friendly_name or self.name
|
return self.current_friendly_name or self.name
|
||||||
|
|
||||||
|
def is_usb_connected(self, devices_on_system, debug=False,
|
||||||
|
only_presence=False):
|
||||||
|
# We manage device presence ourselves, so this method should always
|
||||||
|
# return False
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,71 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
|
|
||||||
from __future__ import (unicode_literals, division, absolute_import,
|
|
||||||
print_function)
|
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
|
||||||
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
|
||||||
__docformat__ = 'restructuredtext en'
|
|
||||||
|
|
||||||
from calibre.constants import plugins
|
|
||||||
|
|
||||||
class MTPDetect(object):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
p = plugins['libmtp']
|
|
||||||
self.libmtp = p[0]
|
|
||||||
if self.libmtp is None:
|
|
||||||
print ('Failed to load libmtp, MTP device detection disabled')
|
|
||||||
print (p[1])
|
|
||||||
self.cache = {}
|
|
||||||
|
|
||||||
def __call__(self, devices):
|
|
||||||
'''
|
|
||||||
Given a list of devices as returned by LinuxScanner, return the set of
|
|
||||||
devices that are likely to be MTP devices. This class maintains a cache
|
|
||||||
to minimize USB polling. Note that detection is partially based on a
|
|
||||||
list of known vendor and product ids. This is because polling some
|
|
||||||
older devices causes problems. Therefore, if this method identifies a
|
|
||||||
device as MTP, it is not actually guaranteed that it will be a working
|
|
||||||
MTP device.
|
|
||||||
'''
|
|
||||||
# First drop devices that have been disconnected from the cache
|
|
||||||
connected_devices = {(d.busnum, d.devnum, d.vendor_id, d.product_id,
|
|
||||||
d.bcd, d.serial) for d in devices}
|
|
||||||
for d in tuple(self.cache.iterkeys()):
|
|
||||||
if d not in connected_devices:
|
|
||||||
del self.cache[d]
|
|
||||||
|
|
||||||
# Since is_mtp_device() can cause USB traffic by probing the device, we
|
|
||||||
# cache its result
|
|
||||||
mtp_devices = set()
|
|
||||||
if self.libmtp is None:
|
|
||||||
return mtp_devices
|
|
||||||
|
|
||||||
for d in devices:
|
|
||||||
ans = self.cache.get((d.busnum, d.devnum, d.vendor_id, d.product_id,
|
|
||||||
d.bcd, d.serial), None)
|
|
||||||
if ans is None:
|
|
||||||
ans = self.libmtp.is_mtp_device(d.busnum, d.devnum,
|
|
||||||
d.vendor_id, d.product_id)
|
|
||||||
self.cache[(d.busnum, d.devnum, d.vendor_id, d.product_id,
|
|
||||||
d.bcd, d.serial)] = ans
|
|
||||||
if ans:
|
|
||||||
mtp_devices.add(d)
|
|
||||||
return mtp_devices
|
|
||||||
|
|
||||||
def create_device(self, connected_device):
|
|
||||||
d = connected_device
|
|
||||||
return self.libmtp.Device(d.busnum, d.devnum, d.vendor_id,
|
|
||||||
d.product_id, d.manufacturer, d.product, d.serial)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
from calibre.devices.scanner import linux_scanner
|
|
||||||
mtp_detect = MTPDetect()
|
|
||||||
devs = mtp_detect(linux_scanner())
|
|
||||||
print ('Found %d MTP devices:'%len(devs))
|
|
||||||
for dev in devs:
|
|
||||||
print (dev, 'at busnum=%d and devnum=%d'%(dev.busnum, dev.devnum))
|
|
||||||
print()
|
|
||||||
|
|
||||||
|
|
@ -10,11 +10,19 @@ __docformat__ = 'restructuredtext en'
|
|||||||
import time, operator
|
import time, operator
|
||||||
from threading import RLock
|
from threading import RLock
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
from calibre.constants import plugins
|
||||||
from calibre.devices.errors import OpenFailed, DeviceError
|
from calibre.devices.errors import OpenFailed, DeviceError
|
||||||
from calibre.devices.mtp.base import MTPDeviceBase, synchronous
|
from calibre.devices.mtp.base import MTPDeviceBase, synchronous
|
||||||
from calibre.devices.mtp.filesystem_cache import FilesystemCache
|
from calibre.devices.mtp.filesystem_cache import FilesystemCache
|
||||||
from calibre.devices.mtp.unix.detect import MTPDetect
|
|
||||||
|
MTPDevice = namedtuple('MTPDevice', 'busnum devnum vendor_id product_id '
|
||||||
|
'bcd serial manufacturer product')
|
||||||
|
|
||||||
|
def fingerprint(d):
|
||||||
|
return MTPDevice(d.busnum, d.devnum, d.vendor_id, d.product_id, d.bcd,
|
||||||
|
d.serial, d.manufacturer, d.product)
|
||||||
|
|
||||||
class MTP_DEVICE(MTPDeviceBase):
|
class MTP_DEVICE(MTPDeviceBase):
|
||||||
|
|
||||||
@ -22,13 +30,18 @@ class MTP_DEVICE(MTPDeviceBase):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
MTPDeviceBase.__init__(self, *args, **kwargs)
|
MTPDeviceBase.__init__(self, *args, **kwargs)
|
||||||
|
self.libmtp = None
|
||||||
|
self.detect_cache = {}
|
||||||
|
|
||||||
self.dev = None
|
self.dev = None
|
||||||
self._filesystem_cache = None
|
self._filesystem_cache = None
|
||||||
self.lock = RLock()
|
self.lock = RLock()
|
||||||
self.blacklisted_devices = set()
|
self.blacklisted_devices = set()
|
||||||
|
self.ejected_devices = set()
|
||||||
|
self.currently_connected_dev = None
|
||||||
|
|
||||||
def set_debug_level(self, lvl):
|
def set_debug_level(self, lvl):
|
||||||
self.detect.libmtp.set_debug_level(lvl)
|
self.libmtp.set_debug_level(lvl)
|
||||||
|
|
||||||
def report_progress(self, sent, total):
|
def report_progress(self, sent, total):
|
||||||
try:
|
try:
|
||||||
@ -39,40 +52,67 @@ class MTP_DEVICE(MTPDeviceBase):
|
|||||||
self.progress_reporter(p)
|
self.progress_reporter(p)
|
||||||
|
|
||||||
@synchronous
|
@synchronous
|
||||||
def is_usb_connected(self, devices_on_system, debug=False,
|
def detect_managed_devices(self, devices_on_system):
|
||||||
only_presence=False):
|
if self.libmtp is None: return None
|
||||||
|
|
||||||
# First remove blacklisted devices.
|
# First remove blacklisted devices.
|
||||||
devs = []
|
devs = set()
|
||||||
for d in devices_on_system:
|
for d in devices_on_system:
|
||||||
if (d.busnum, d.devnum, d.vendor_id,
|
fp = fingerprint(d)
|
||||||
d.product_id, d.bcd, d.serial) not in self.blacklisted_devices:
|
if fp not in self.blacklisted_devices:
|
||||||
devs.append(d)
|
devs.add(fp)
|
||||||
|
|
||||||
devs = self.detect(devs)
|
# Clean up ejected devices
|
||||||
if self.dev is not None:
|
self.ejected_devices = devs.intersection(self.ejected_devices)
|
||||||
# Check if the currently opened device is still connected
|
|
||||||
ids = self.dev.ids
|
# Check if the currently connected device is still present
|
||||||
found = False
|
if self.currently_connected_dev is not None:
|
||||||
for d in devs:
|
return (self.currently_connected_dev if
|
||||||
if ( (d.busnum, d.devnum, d.vendor_id, d.product_id, d.serial)
|
self.currently_connected_dev in devs else None)
|
||||||
== ids ):
|
|
||||||
found = True
|
# Remove ejected devices
|
||||||
break
|
devs = devs - self.ejected_devices
|
||||||
return found
|
|
||||||
# Check if any MTP capable device is present
|
# Now check for MTP devices
|
||||||
return len(devs) > 0
|
cache = self.detect_cache
|
||||||
|
for d in devs:
|
||||||
|
ans = cache.get(d, None)
|
||||||
|
if ans is None:
|
||||||
|
ans = self.libmtp.is_mtp_device(d.busnum, d.devnum,
|
||||||
|
d.vendor_id, d.product_id)
|
||||||
|
cache[d] = ans
|
||||||
|
if ans:
|
||||||
|
return d
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
@synchronous
|
||||||
|
def create_device(self, connected_device):
|
||||||
|
d = connected_device
|
||||||
|
return self.libmtp.Device(d.busnum, d.devnum, d.vendor_id,
|
||||||
|
d.product_id, d.manufacturer, d.product, d.serial)
|
||||||
|
|
||||||
|
@synchronous
|
||||||
|
def eject(self):
|
||||||
|
if self.currently_connected_dev is None: return
|
||||||
|
self.ejected_devices.add(self.currently_connected_dev)
|
||||||
|
self.post_yank_cleanup()
|
||||||
|
|
||||||
@synchronous
|
@synchronous
|
||||||
def post_yank_cleanup(self):
|
def post_yank_cleanup(self):
|
||||||
self.dev = self._filesystem_cache = self.current_friendly_name = None
|
self.dev = self._filesystem_cache = self.current_friendly_name = None
|
||||||
|
self.currently_connected_dev = None
|
||||||
|
|
||||||
@synchronous
|
@synchronous
|
||||||
def startup(self):
|
def startup(self):
|
||||||
self.detect = MTPDetect()
|
p = plugins['libmtp']
|
||||||
for x in vars(self.detect.libmtp):
|
self.libmtp = p[0]
|
||||||
|
if self.libmtp is None:
|
||||||
|
print ('Failed to load libmtp, MTP device detection disabled')
|
||||||
|
print (p[1])
|
||||||
|
|
||||||
|
for x in vars(self.libmtp):
|
||||||
if x.startswith('LIBMTP'):
|
if x.startswith('LIBMTP'):
|
||||||
setattr(self, x, getattr(self.detect.libmtp, x))
|
setattr(self, x, getattr(self.libmtp, x))
|
||||||
|
|
||||||
@synchronous
|
@synchronous
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
@ -85,29 +125,25 @@ class MTP_DEVICE(MTPDeviceBase):
|
|||||||
@synchronous
|
@synchronous
|
||||||
def open(self, connected_device, library_uuid):
|
def open(self, connected_device, library_uuid):
|
||||||
self.dev = self._filesystem_cache = None
|
self.dev = self._filesystem_cache = None
|
||||||
def blacklist_device():
|
|
||||||
d = connected_device
|
|
||||||
self.blacklisted_devices.add((d.busnum, d.devnum, d.vendor_id,
|
|
||||||
d.product_id, d.bcd, d.serial))
|
|
||||||
try:
|
try:
|
||||||
self.dev = self.detect.create_device(connected_device)
|
self.dev = self.create_device(connected_device)
|
||||||
except ValueError:
|
except self.libmtp.MTPError:
|
||||||
# Give the device some time to settle
|
# Give the device some time to settle
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
try:
|
try:
|
||||||
self.dev = self.detect.create_device(connected_device)
|
self.dev = self.create_device(connected_device)
|
||||||
except ValueError:
|
except self.libmtp.MTPError:
|
||||||
# Black list this device so that it is ignored for the
|
# Black list this device so that it is ignored for the
|
||||||
# remainder of this session.
|
# remainder of this session.
|
||||||
blacklist_device()
|
self.blacklisted_devices.add(connected_device)
|
||||||
raise OpenFailed('%s is not a MTP device'%(connected_device,))
|
raise OpenFailed('%s is not a MTP device'%(connected_device,))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
blacklist_device()
|
self.blacklisted_devices.add(connected_device)
|
||||||
raise OpenFailed('')
|
raise OpenFailed('')
|
||||||
|
|
||||||
storage = sorted(self.dev.storage_info, key=operator.itemgetter('id'))
|
storage = sorted(self.dev.storage_info, key=operator.itemgetter('id'))
|
||||||
if not storage:
|
if not storage:
|
||||||
blacklist_device()
|
self.blacklisted_devices.add(connected_device)
|
||||||
raise OpenFailed('No storage found for device %s'%(connected_device,))
|
raise OpenFailed('No storage found for device %s'%(connected_device,))
|
||||||
self._main_id = storage[0]['id']
|
self._main_id = storage[0]['id']
|
||||||
self._carda_id = self._cardb_id = None
|
self._carda_id = self._cardb_id = None
|
||||||
|
Loading…
x
Reference in New Issue
Block a user