mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Better handling of smart device disconnects during initialization, and especially password mismatches.
This commit is contained in:
parent
8e4c24c8a2
commit
df2c0a2106
@ -48,6 +48,11 @@ class OpenFeedback(DeviceError):
|
|||||||
'''
|
'''
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
class OpenFailed(ProtocolError):
|
||||||
|
""" Raised when device cannot be opened this time. No retry is to be done.
|
||||||
|
The device should continue to be polled for future opens. If the
|
||||||
|
message is empty, no exception trace is produced. """
|
||||||
|
|
||||||
class DeviceBusy(ProtocolError):
|
class DeviceBusy(ProtocolError):
|
||||||
""" Raised when device is busy """
|
""" Raised when device is busy """
|
||||||
def __init__(self, uerr=""):
|
def __init__(self, uerr=""):
|
||||||
|
@ -14,7 +14,7 @@ from functools import wraps
|
|||||||
|
|
||||||
from calibre import prints
|
from calibre import prints
|
||||||
from calibre.constants import numeric_version, DEBUG
|
from calibre.constants import numeric_version, DEBUG
|
||||||
from calibre.devices.errors import OpenFeedback
|
from calibre.devices.errors import OpenFailed, ControlError, TimeoutError
|
||||||
from calibre.devices.interface import DevicePlugin
|
from calibre.devices.interface import DevicePlugin
|
||||||
from calibre.devices.usbms.books import Book, BookList
|
from calibre.devices.usbms.books import Book, BookList
|
||||||
from calibre.devices.usbms.deviceconfig import DeviceConfig
|
from calibre.devices.usbms.deviceconfig import DeviceConfig
|
||||||
@ -353,24 +353,18 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
self._debug('protocol error -- empty json string')
|
self._debug('protocol error -- empty json string')
|
||||||
except socket.timeout:
|
except socket.timeout:
|
||||||
self._debug('timeout communicating with device')
|
self._debug('timeout communicating with device')
|
||||||
self.device_socket.close()
|
self._close_device_socket()
|
||||||
self.device_socket = None
|
raise TimeoutError('Device did not respond in reasonable time')
|
||||||
self.is_connected = False
|
|
||||||
raise IOError(_('Device did not respond in reasonable time'))
|
|
||||||
except socket.error:
|
except socket.error:
|
||||||
self._debug('device went away')
|
self._debug('device went away')
|
||||||
self.device_socket.close()
|
self._close_device_socket()
|
||||||
self.device_socket = None
|
raise ControlError('Device closed the network connection')
|
||||||
self.is_connected = False
|
|
||||||
raise IOError(_('Device closed the network connection'))
|
|
||||||
except:
|
except:
|
||||||
self._debug('other exception')
|
self._debug('other exception')
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
self.device_socket.close()
|
self._close_device_socket()
|
||||||
self.device_socket = None
|
|
||||||
self.is_connected = False
|
|
||||||
raise
|
raise
|
||||||
raise IOError('Device responded with incorrect information')
|
raise ControlError('Device responded with incorrect information')
|
||||||
|
|
||||||
# Write a file as a series of base64-encoded strings.
|
# Write a file as a series of base64-encoded strings.
|
||||||
def _put_file(self, infile, lpath, book_metadata, this_book, total_books):
|
def _put_file(self, infile, lpath, book_metadata, this_book, total_books):
|
||||||
@ -449,8 +443,16 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
else:
|
else:
|
||||||
self.known_metadata[lpath] = book.deepcopy()
|
self.known_metadata[lpath] = book.deepcopy()
|
||||||
|
|
||||||
# The public interface methods.
|
def _close_device_socket(self):
|
||||||
|
if self.device_socket is not None:
|
||||||
|
try:
|
||||||
|
self.device_socket.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self.device_socket = None
|
||||||
|
self.is_connected = False
|
||||||
|
|
||||||
|
# The public interface methods.
|
||||||
|
|
||||||
@synchronous('sync_lock')
|
@synchronous('sync_lock')
|
||||||
def is_usb_connected(self, devices_on_system, debug=False, only_presence=False):
|
def is_usb_connected(self, devices_on_system, debug=False, only_presence=False):
|
||||||
@ -471,11 +473,9 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
if self._call_client('NOOP', dict())[0] is None:
|
if self._call_client('NOOP', dict())[0] is None:
|
||||||
self.is_connected = False
|
self._close_device_socket()
|
||||||
except:
|
except:
|
||||||
self.is_connected = False
|
self._close_device_socket()
|
||||||
if not self.is_connected:
|
|
||||||
self.device_socket.close()
|
|
||||||
return (self.is_connected, self)
|
return (self.is_connected, self)
|
||||||
if getattr(self, 'listen_socket', None) is not None:
|
if getattr(self, 'listen_socket', None) is not None:
|
||||||
ans = select.select((self.listen_socket,), (), (), 0)
|
ans = select.select((self.listen_socket,), (), (), 0)
|
||||||
@ -491,15 +491,11 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
self.device_socket.settimeout(None)
|
self.device_socket.settimeout(None)
|
||||||
self.is_connected = True
|
self.is_connected = True
|
||||||
except socket.timeout:
|
except socket.timeout:
|
||||||
if self.device_socket is not None:
|
self._close_device_socket()
|
||||||
self.device_socket.close()
|
|
||||||
self.is_connected = False
|
|
||||||
except socket.error:
|
except socket.error:
|
||||||
x = sys.exc_info()[1]
|
x = sys.exc_info()[1]
|
||||||
self._debug('unexpected socket exception', x.args[0])
|
self._debug('unexpected socket exception', x.args[0])
|
||||||
if self.device_socket is not None:
|
self._close_device_socket()
|
||||||
self.device_socket.close()
|
|
||||||
self.is_connected = False
|
|
||||||
raise
|
raise
|
||||||
return (True, self)
|
return (True, self)
|
||||||
return (False, None)
|
return (False, None)
|
||||||
@ -507,6 +503,9 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
@synchronous('sync_lock')
|
@synchronous('sync_lock')
|
||||||
def open(self, connected_device, library_uuid):
|
def open(self, connected_device, library_uuid):
|
||||||
self._debug()
|
self._debug()
|
||||||
|
if not self.is_connected:
|
||||||
|
# We have been called to retry the connection. Give up immediately
|
||||||
|
raise ControlError('Attempt to open a closed device')
|
||||||
self.current_library_uuid = library_uuid
|
self.current_library_uuid = library_uuid
|
||||||
self.current_library_name = current_library_name()
|
self.current_library_name = current_library_name()
|
||||||
try:
|
try:
|
||||||
@ -530,28 +529,24 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
# Something wrong with the return. Close the socket
|
# Something wrong with the return. Close the socket
|
||||||
# and continue.
|
# and continue.
|
||||||
self._debug('Protocol error - Opcode not OK')
|
self._debug('Protocol error - Opcode not OK')
|
||||||
self.device_socket.close()
|
self._close_device_socket()
|
||||||
self.is_connected = False
|
|
||||||
return False
|
return False
|
||||||
if not result.get('versionOK', False):
|
if not result.get('versionOK', False):
|
||||||
# protocol mismatch
|
# protocol mismatch
|
||||||
self._debug('Protocol error - protocol version mismatch')
|
self._debug('Protocol error - protocol version mismatch')
|
||||||
self.device_socket.close()
|
self._close_device_socket()
|
||||||
self.is_connected = False
|
|
||||||
return False
|
return False
|
||||||
if result.get('maxBookContentPacketLen', 0) <= 0:
|
if result.get('maxBookContentPacketLen', 0) <= 0:
|
||||||
# protocol mismatch
|
# protocol mismatch
|
||||||
self._debug('Protocol error - bogus book packet length')
|
self._debug('Protocol error - bogus book packet length')
|
||||||
self.device_socket.close()
|
self._close_device_socket()
|
||||||
self.is_connected = False
|
|
||||||
return False
|
return False
|
||||||
self.max_book_packet_len = result.get('maxBookContentPacketLen',
|
self.max_book_packet_len = result.get('maxBookContentPacketLen',
|
||||||
self.BASE_PACKET_LEN)
|
self.BASE_PACKET_LEN)
|
||||||
exts = result.get('acceptedExtensions', None)
|
exts = result.get('acceptedExtensions', None)
|
||||||
if exts is None or not isinstance(exts, list) or len(exts) == 0:
|
if exts is None or not isinstance(exts, list) or len(exts) == 0:
|
||||||
self._debug('Protocol error - bogus accepted extensions')
|
self._debug('Protocol error - bogus accepted extensions')
|
||||||
self.device_socket.close()
|
self._close_device_socket()
|
||||||
self.is_connected = False
|
|
||||||
return False
|
return False
|
||||||
self.FORMATS = exts
|
self.FORMATS = exts
|
||||||
if password:
|
if password:
|
||||||
@ -559,25 +554,29 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
if result.get('passwordHash', None) is None:
|
if result.get('passwordHash', None) is None:
|
||||||
# protocol mismatch
|
# protocol mismatch
|
||||||
self._debug('Protocol error - missing password hash')
|
self._debug('Protocol error - missing password hash')
|
||||||
self.device_socket.close()
|
self._close_device_socket()
|
||||||
self.is_connected = False
|
|
||||||
return False
|
return False
|
||||||
if returned_hash != hash_digest:
|
if returned_hash != hash_digest:
|
||||||
# bad password
|
# bad password
|
||||||
self._debug('password mismatch')
|
self._debug('password mismatch')
|
||||||
self._call_client("DISPLAY_MESSAGE", {'messageKind':1})
|
try:
|
||||||
self.is_connected = False
|
self._call_client("DISPLAY_MESSAGE",
|
||||||
self.device_socket.close()
|
{'messageKind':1,
|
||||||
raise OpenFeedback('Incorrect password supplied')
|
'currentLibraryName': self.current_library_name,
|
||||||
|
'currentLibraryUUID': library_uuid})
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self._close_device_socket()
|
||||||
|
# Don't bother with a message. The user will be informed on
|
||||||
|
# the device.
|
||||||
|
raise OpenFailed('')
|
||||||
return True
|
return True
|
||||||
except socket.timeout:
|
except socket.timeout:
|
||||||
self.device_socket.close()
|
self._close_device_socket()
|
||||||
self.is_connected = False
|
|
||||||
except socket.error:
|
except socket.error:
|
||||||
x = sys.exc_info()[1]
|
x = sys.exc_info()[1]
|
||||||
self._debug('unexpected socket exception', x.args[0])
|
self._debug('unexpected socket exception', x.args[0])
|
||||||
self.device_socket.close()
|
self._close_device_socket()
|
||||||
self.is_connected = False
|
|
||||||
raise
|
raise
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -656,7 +655,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
self._set_known_metadata(book)
|
self._set_known_metadata(book)
|
||||||
bl.add_book(book, replace_metadata=True)
|
bl.add_book(book, replace_metadata=True)
|
||||||
else:
|
else:
|
||||||
raise IOError(_('Protocol error -- book metadata not returned'))
|
raise ControlError('book metadata not returned')
|
||||||
return bl
|
return bl
|
||||||
|
|
||||||
@synchronous('sync_lock')
|
@synchronous('sync_lock')
|
||||||
@ -675,15 +674,12 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
print_debug_info=False)
|
print_debug_info=False)
|
||||||
if opcode != 'OK':
|
if opcode != 'OK':
|
||||||
self._debug('protocol error', opcode, i)
|
self._debug('protocol error', opcode, i)
|
||||||
raise IOError(_('Protocol error -- sync_booklists'))
|
raise ControlError('sync_booklists')
|
||||||
|
|
||||||
@synchronous('sync_lock')
|
@synchronous('sync_lock')
|
||||||
def eject(self):
|
def eject(self):
|
||||||
self._debug()
|
self._debug()
|
||||||
if self.device_socket:
|
self._close_device_socket()
|
||||||
self.device_socket.close()
|
|
||||||
self.device_socket = None
|
|
||||||
self.is_connected = False
|
|
||||||
|
|
||||||
@synchronous('sync_lock')
|
@synchronous('sync_lock')
|
||||||
def post_yank_cleanup(self):
|
def post_yank_cleanup(self):
|
||||||
@ -706,7 +702,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
book = Book(self.PREFIX, lpath, other=mdata)
|
book = Book(self.PREFIX, lpath, other=mdata)
|
||||||
length = self._put_file(infile, lpath, book, i, len(files))
|
length = self._put_file(infile, lpath, book, i, len(files))
|
||||||
if length < 0:
|
if length < 0:
|
||||||
raise IOError(_('Sending book %s to device failed') % lpath)
|
raise ControlError('Sending book %s to device failed' % lpath)
|
||||||
paths.append((lpath, length))
|
paths.append((lpath, length))
|
||||||
# No need to deal with covers. The client will get the thumbnails
|
# No need to deal with covers. The client will get the thumbnails
|
||||||
# in the mi structure
|
# in the mi structure
|
||||||
@ -747,7 +743,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
if opcode == 'OK':
|
if opcode == 'OK':
|
||||||
self._debug('removed book with UUID', result['uuid'])
|
self._debug('removed book with UUID', result['uuid'])
|
||||||
else:
|
else:
|
||||||
raise IOError(_('Protocol error - delete books'))
|
raise ControlError('Protocol error - delete books')
|
||||||
|
|
||||||
@synchronous('sync_lock')
|
@synchronous('sync_lock')
|
||||||
def remove_books_from_metadata(self, paths, booklists):
|
def remove_books_from_metadata(self, paths, booklists):
|
||||||
@ -783,7 +779,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
else:
|
else:
|
||||||
eof = True
|
eof = True
|
||||||
else:
|
else:
|
||||||
raise IOError(_('request for book data failed'))
|
raise ControlError('request for book data failed')
|
||||||
|
|
||||||
@synchronous('sync_lock')
|
@synchronous('sync_lock')
|
||||||
def set_plugboards(self, plugboards, pb_func):
|
def set_plugboards(self, plugboards, pb_func):
|
||||||
|
@ -13,7 +13,7 @@ from PyQt4.Qt import (QMenu, QAction, QActionGroup, QIcon, SIGNAL,
|
|||||||
from calibre.customize.ui import (available_input_formats, available_output_formats,
|
from calibre.customize.ui import (available_input_formats, available_output_formats,
|
||||||
device_plugins)
|
device_plugins)
|
||||||
from calibre.devices.interface import DevicePlugin
|
from calibre.devices.interface import DevicePlugin
|
||||||
from calibre.devices.errors import UserFeedback, OpenFeedback
|
from calibre.devices.errors import UserFeedback, OpenFeedback, OpenFailed
|
||||||
from calibre.gui2.dialogs.choose_format_device import ChooseFormatDeviceDialog
|
from calibre.gui2.dialogs.choose_format_device import ChooseFormatDeviceDialog
|
||||||
from calibre.utils.ipc.job import BaseJob
|
from calibre.utils.ipc.job import BaseJob
|
||||||
from calibre.devices.scanner import DeviceScanner
|
from calibre.devices.scanner import DeviceScanner
|
||||||
@ -172,6 +172,8 @@ class DeviceManager(Thread): # {{{
|
|||||||
self.open_feedback_msg(dev.get_gui_name(), e)
|
self.open_feedback_msg(dev.get_gui_name(), e)
|
||||||
self.ejected_devices.add(dev)
|
self.ejected_devices.add(dev)
|
||||||
continue
|
continue
|
||||||
|
except OpenFailed:
|
||||||
|
raise
|
||||||
except:
|
except:
|
||||||
tb = traceback.format_exc()
|
tb = traceback.format_exc()
|
||||||
if DEBUG or tb not in self.reported_errors:
|
if DEBUG or tb not in self.reported_errors:
|
||||||
@ -225,6 +227,7 @@ class DeviceManager(Thread): # {{{
|
|||||||
only_presence=True, debug=True)
|
only_presence=True, debug=True)
|
||||||
self.connected_device_removed()
|
self.connected_device_removed()
|
||||||
else:
|
else:
|
||||||
|
try:
|
||||||
possibly_connected_devices = []
|
possibly_connected_devices = []
|
||||||
for device in self.devices:
|
for device in self.devices:
|
||||||
if device in self.ejected_devices:
|
if device in self.ejected_devices:
|
||||||
@ -243,6 +246,9 @@ class DeviceManager(Thread): # {{{
|
|||||||
device_kind='usb'):
|
device_kind='usb'):
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
prints('Device connect failed again, giving up')
|
prints('Device connect failed again, giving up')
|
||||||
|
except OpenFailed as e:
|
||||||
|
if str(e):
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
# Mount devices that don't use USB, such as the folder device and iTunes
|
# Mount devices that don't use USB, such as the folder device and iTunes
|
||||||
# This will be called on the GUI thread. Because of this, we must store
|
# This will be called on the GUI thread. Because of this, we must store
|
||||||
|
Loading…
x
Reference in New Issue
Block a user