OS X/Linux: Show an informational popup message when an Android device is plugged in that needs the user to tap Allow for the connection to work.

This commit is contained in:
Kovid Goyal 2017-02-03 18:06:33 +05:30
parent c4931a2fcd
commit 6a82169373
3 changed files with 49 additions and 11 deletions

View File

@ -1,4 +1,4 @@
__license__ = 'GPL v3'
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
"""
Defines the errors that the device drivers generate.
@ -18,8 +18,11 @@ class TimeoutError(ProtocolError):
""" There was a timeout during communication """
def __init__(self, func_name):
ProtocolError.__init__(self,
"There was a timeout while communicating with the device in function: " +func_name)
ProtocolError.__init__(
self,
"There was a timeout while communicating with the device in function: " +
func_name
)
class DeviceError(ProtocolError):
@ -54,7 +57,17 @@ class OpenFeedback(DeviceError):
If you need to show the user a custom dialog, instead of just
displaying the feedback_msg, create and return it here.
'''
raise NotImplementedError
raise NotImplementedError()
class OpenActionNeeded(DeviceError):
def __init__(self, device_name, msg, only_once_id):
self.device_name, self.feedback_msg, self.only_once_id = device_name, msg, only_once_id
DeviceError.__init__(self, msg)
def custom_dialog(self, parent):
raise NotImplementedError()
class InitialConnectionError(OpenFeedback):
@ -76,8 +89,10 @@ class DeviceBusy(ProtocolError):
""" Raised when device is busy """
def __init__(self, uerr=""):
ProtocolError.__init__(self, "Device is in use by another application:"
"\nUnderlying error:" + str(uerr))
ProtocolError.__init__(
self, "Device is in use by another application:"
"\nUnderlying error:" + str(uerr)
)
class DeviceLocked(ProtocolError):
@ -138,4 +153,3 @@ class BlacklistedDevice(OpenFailed):
blacklisted by the user. Only used in drivers that manage device presence,
like the MTP driver. '''
pass

View File

@ -15,7 +15,7 @@ from functools import partial
from calibre import prints, as_unicode
from calibre.constants import plugins, islinux, isosx
from calibre.ptempfile import SpooledTemporaryFile
from calibre.devices.errors import OpenFailed, DeviceError, BlacklistedDevice
from calibre.devices.errors import OpenFailed, DeviceError, BlacklistedDevice, OpenActionNeeded
from calibre.devices.mtp.base import MTPDeviceBase, synchronous, debug
MTPDevice = namedtuple('MTPDevice', 'busnum devnum vendor_id product_id '
@ -28,6 +28,7 @@ def fingerprint(d):
return MTPDevice(d.busnum, d.devnum, d.vendor_id, d.product_id, d.bcd,
d.serial, d.manufacturer, d.product)
APPLE = 0x05ac
@ -211,6 +212,7 @@ class MTP_DEVICE(MTPDeviceBase):
@synchronous
def open(self, connected_device, library_uuid):
self.dev = self._filesystem_cache = None
try:
self.dev = self.create_device(connected_device)
except Exception as e:
@ -218,7 +220,23 @@ class MTP_DEVICE(MTPDeviceBase):
raise OpenFailed('Failed to open %s: Error: %s'%(
connected_device, as_unicode(e)))
storage = sorted(self.dev.storage_info, key=operator.itemgetter('id'))
try:
storage = sorted(self.dev.storage_info, key=operator.itemgetter('id'))
except self.libmtp.MTPError as e:
if "The device has no storage information." in str(e):
# This happens on newer Android devices while waiting for
# the user to allow access. Apparently what happens is
# that when the user clicks allow, the device disconnects
# and re-connects as a new device.
raise OpenActionNeeded(self.dev.friendly_name, _(
'The device {0} is not allowing connections.'
' Unlock the screen on the {0}, tap "Allow" on any connection popup message you see,'
' then either wait a minute or restart calibre. You might'
' also have to change the mode of the USB connection on the {0}'
' to "Media Transfer mode (MTP)" or similar.'
).format(self.dev.friendly_name), (self.dev.friendly_name, self.dev.serial_number))
raise
storage = [x for x in storage if x.get('rw', False)]
if not storage:
self.blacklisted_devices.add(connected_device)
@ -432,6 +450,7 @@ def develop():
finally:
dev.shutdown()
if __name__ == '__main__':
dev = MTP_DEVICE(None)
dev.startup()
@ -442,4 +461,3 @@ if __name__ == '__main__':
dev.debug_managed_device_detection(devs, sys.stdout)
dev.set_debug_level(dev.LIBMTP_DEBUG_ALL)
dev.shutdown()

View File

@ -14,7 +14,7 @@ from PyQt5.Qt import (
from calibre.customize.ui import (available_input_formats, available_output_formats,
device_plugins, disabled_device_plugins)
from calibre.devices.interface import DevicePlugin, currently_connected_device
from calibre.devices.errors import (UserFeedback, OpenFeedback, OpenFailed,
from calibre.devices.errors import (UserFeedback, OpenFeedback, OpenFailed, OpenActionNeeded,
InitialConnectionError)
from calibre.ebooks.covers import cprefs, override_prefs, scale_cover, generate_cover
from calibre.gui2.dialogs.choose_format_device import ChooseFormatDeviceDialog
@ -165,6 +165,7 @@ class DeviceManager(Thread): # {{{
self.ejected_devices = set([])
self.mount_connection_requests = Queue.Queue(0)
self.open_feedback_slot = open_feedback_slot
self.open_feedback_only_once_seen = set()
self.after_callback_feedback_slot = after_callback_feedback_slot
self.open_feedback_msg = open_feedback_msg
self._device_information = None
@ -296,6 +297,10 @@ class DeviceManager(Thread): # {{{
except BlacklistedDevice as e:
prints('Ignoring blacklisted device: %s'%
as_unicode(e))
except OpenActionNeeded as e:
if e.only_once_id not in self.open_feedback_only_once_seen:
self.open_feedback_only_once_seen.add(e.only_once_id)
self.open_feedback_msg(e.device_name, e)
except:
prints('Error while trying to open %s (Driver: %s)'%
(cd, dev))
@ -875,6 +880,7 @@ class DeviceSignals(QObject): # {{{
#: otherwise a disconnection.
device_connection_changed = pyqtSignal(object)
device_signals = DeviceSignals()
# }}}