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'
|
||||
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
|
||||
CAN_SET_METADATA = []
|
||||
|
||||
@ -51,4 +46,10 @@ class MTPDeviceBase(DevicePlugin):
|
||||
def get_gui_name(self):
|
||||
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
|
||||
from threading import RLock
|
||||
from io import BytesIO
|
||||
from collections import namedtuple
|
||||
|
||||
from calibre.constants import plugins
|
||||
from calibre.devices.errors import OpenFailed, DeviceError
|
||||
from calibre.devices.mtp.base import MTPDeviceBase, synchronous
|
||||
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):
|
||||
|
||||
@ -22,13 +30,18 @@ class MTP_DEVICE(MTPDeviceBase):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
MTPDeviceBase.__init__(self, *args, **kwargs)
|
||||
self.libmtp = None
|
||||
self.detect_cache = {}
|
||||
|
||||
self.dev = None
|
||||
self._filesystem_cache = None
|
||||
self.lock = RLock()
|
||||
self.blacklisted_devices = set()
|
||||
self.ejected_devices = set()
|
||||
self.currently_connected_dev = None
|
||||
|
||||
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):
|
||||
try:
|
||||
@ -39,40 +52,67 @@ class MTP_DEVICE(MTPDeviceBase):
|
||||
self.progress_reporter(p)
|
||||
|
||||
@synchronous
|
||||
def is_usb_connected(self, devices_on_system, debug=False,
|
||||
only_presence=False):
|
||||
|
||||
def detect_managed_devices(self, devices_on_system):
|
||||
if self.libmtp is None: return None
|
||||
# First remove blacklisted devices.
|
||||
devs = []
|
||||
devs = set()
|
||||
for d in devices_on_system:
|
||||
if (d.busnum, d.devnum, d.vendor_id,
|
||||
d.product_id, d.bcd, d.serial) not in self.blacklisted_devices:
|
||||
devs.append(d)
|
||||
fp = fingerprint(d)
|
||||
if fp not in self.blacklisted_devices:
|
||||
devs.add(fp)
|
||||
|
||||
devs = self.detect(devs)
|
||||
if self.dev is not None:
|
||||
# Check if the currently opened device is still connected
|
||||
ids = self.dev.ids
|
||||
found = False
|
||||
for d in devs:
|
||||
if ( (d.busnum, d.devnum, d.vendor_id, d.product_id, d.serial)
|
||||
== ids ):
|
||||
found = True
|
||||
break
|
||||
return found
|
||||
# Check if any MTP capable device is present
|
||||
return len(devs) > 0
|
||||
# Clean up ejected devices
|
||||
self.ejected_devices = devs.intersection(self.ejected_devices)
|
||||
|
||||
# Check if the currently connected device is still present
|
||||
if self.currently_connected_dev is not None:
|
||||
return (self.currently_connected_dev if
|
||||
self.currently_connected_dev in devs else None)
|
||||
|
||||
# Remove ejected devices
|
||||
devs = devs - self.ejected_devices
|
||||
|
||||
# Now check for MTP devices
|
||||
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
|
||||
def post_yank_cleanup(self):
|
||||
self.dev = self._filesystem_cache = self.current_friendly_name = None
|
||||
self.currently_connected_dev = None
|
||||
|
||||
@synchronous
|
||||
def startup(self):
|
||||
self.detect = MTPDetect()
|
||||
for x in vars(self.detect.libmtp):
|
||||
p = plugins['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'):
|
||||
setattr(self, x, getattr(self.detect.libmtp, x))
|
||||
setattr(self, x, getattr(self.libmtp, x))
|
||||
|
||||
@synchronous
|
||||
def shutdown(self):
|
||||
@ -85,29 +125,25 @@ class MTP_DEVICE(MTPDeviceBase):
|
||||
@synchronous
|
||||
def open(self, connected_device, library_uuid):
|
||||
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:
|
||||
self.dev = self.detect.create_device(connected_device)
|
||||
except ValueError:
|
||||
self.dev = self.create_device(connected_device)
|
||||
except self.libmtp.MTPError:
|
||||
# Give the device some time to settle
|
||||
time.sleep(2)
|
||||
try:
|
||||
self.dev = self.detect.create_device(connected_device)
|
||||
except ValueError:
|
||||
self.dev = self.create_device(connected_device)
|
||||
except self.libmtp.MTPError:
|
||||
# Black list this device so that it is ignored for the
|
||||
# remainder of this session.
|
||||
blacklist_device()
|
||||
self.blacklisted_devices.add(connected_device)
|
||||
raise OpenFailed('%s is not a MTP device'%(connected_device,))
|
||||
except TypeError:
|
||||
blacklist_device()
|
||||
self.blacklisted_devices.add(connected_device)
|
||||
raise OpenFailed('')
|
||||
|
||||
storage = sorted(self.dev.storage_info, key=operator.itemgetter('id'))
|
||||
if not storage:
|
||||
blacklist_device()
|
||||
self.blacklisted_devices.add(connected_device)
|
||||
raise OpenFailed('No storage found for device %s'%(connected_device,))
|
||||
self._main_id = storage[0]['id']
|
||||
self._carda_id = self._cardb_id = None
|
||||
|
Loading…
x
Reference in New Issue
Block a user