diff --git a/src/calibre/devices/errors.py b/src/calibre/devices/errors.py index eb8c533fc8..8cf4e127cd 100644 --- a/src/calibre/devices/errors.py +++ b/src/calibre/devices/errors.py @@ -1,4 +1,4 @@ -__license__ = 'GPL v3' +__license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' """ 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 - diff --git a/src/calibre/devices/mtp/unix/driver.py b/src/calibre/devices/mtp/unix/driver.py index 3fa70720d4..02e7955973 100644 --- a/src/calibre/devices/mtp/unix/driver.py +++ b/src/calibre/devices/mtp/unix/driver.py @@ -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() - diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index 29a8f7d75d..6a18a621e6 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -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() # }}}