From 11731fd0307cd4b0673f1d58457353f410ec46a9 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Wed, 1 Aug 2012 18:05:06 +0200 Subject: [PATCH 1/5] Fix exception when attempting to select on a closed socket --- .../devices/smart_device_app/driver.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/calibre/devices/smart_device_app/driver.py b/src/calibre/devices/smart_device_app/driver.py index 072d8dfc99..72a2ac471a 100644 --- a/src/calibre/devices/smart_device_app/driver.py +++ b/src/calibre/devices/smart_device_app/driver.py @@ -447,18 +447,18 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): if self.is_connected: self.noop_counter += 1 if only_presence and (self.noop_counter % 5) != 1: - ans = select.select((self.device_socket,), (), (), 0) - if len(ans[0]) == 0: - return (True, self) - # The socket indicates that something is there. Given the - # protocol, this can only be a disconnect notification. Fall - # through and actually try to talk to the client. - try: - # This will usually toss an exception if the socket is gone. - if self._call_client('NOOP', dict())[0] is None: + try: + ans = select.select((self.device_socket,), (), (), 0) + if len(ans[0]) == 0: + return (True, self) + # The socket indicates that something is there. Given the + # protocol, this can only be a disconnect notification. Fall + # through and actually try to talk to the client. + # This will usually toss an exception if the socket is gone. + if self._call_client('NOOP', dict())[0] is None: + self.is_connected = False + except: self.is_connected = False - except: - self.is_connected = False if not self.is_connected: self.device_socket.close() return (self.is_connected, self) From 53213fb494f04333d754a34ed0d0ce35c0114c4d Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Wed, 1 Aug 2012 20:36:28 +0200 Subject: [PATCH 2/5] Fix the fix --- src/calibre/devices/smart_device_app/driver.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/calibre/devices/smart_device_app/driver.py b/src/calibre/devices/smart_device_app/driver.py index 72a2ac471a..a235fbf028 100644 --- a/src/calibre/devices/smart_device_app/driver.py +++ b/src/calibre/devices/smart_device_app/driver.py @@ -455,10 +455,13 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): # protocol, this can only be a disconnect notification. Fall # through and actually try to talk to the client. # This will usually toss an exception if the socket is gone. - if self._call_client('NOOP', dict())[0] is None: - self.is_connected = False except: + pass + try: + if self._call_client('NOOP', dict())[0] is None: self.is_connected = False + except: + self.is_connected = False if not self.is_connected: self.device_socket.close() return (self.is_connected, self) From 8ad562c685e349ac6b52daa3edd0707e99255b4f Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Wed, 1 Aug 2012 21:01:40 +0200 Subject: [PATCH 3/5] Temporary fix before adding a socket close method --- src/calibre/devices/smart_device_app/driver.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/calibre/devices/smart_device_app/driver.py b/src/calibre/devices/smart_device_app/driver.py index a235fbf028..9d520abd99 100644 --- a/src/calibre/devices/smart_device_app/driver.py +++ b/src/calibre/devices/smart_device_app/driver.py @@ -346,17 +346,20 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): self._debug('timeout communicating with device') self.device_socket.close() self.device_socket = None + self.is_connected = False raise IOError(_('Device did not respond in reasonable time')) except socket.error: self._debug('device went away') self.device_socket.close() self.device_socket = None + self.is_connected = False raise IOError(_('Device closed the network connection')) except: self._debug('other exception') traceback.print_exc() self.device_socket.close() self.device_socket = None + self.is_connected = False raise raise IOError('Device responded with incorrect information') @@ -481,11 +484,13 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): except socket.timeout: if self.device_socket is not None: self.device_socket.close() + self.is_connected = False except socket.error: x = sys.exc_info()[1] self._debug('unexpected socket exception', x.args[0]) if self.device_socket is not None: self.device_socket.close() + self.is_connected = False raise return (True, self) return (False, None) @@ -517,16 +522,19 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): # and continue. self._debug('Protocol error - Opcode not OK') self.device_socket.close() + self.is_connected = False return False if not result.get('versionOK', False): # protocol mismatch self._debug('Protocol error - protocol version mismatch') self.device_socket.close() + self.is_connected = False return False if result.get('maxBookContentPacketLen', 0) <= 0: # protocol mismatch self._debug('Protocol error - bogus book packet length') self.device_socket.close() + self.is_connected = False return False self.max_book_packet_len = result.get('maxBookContentPacketLen', self.BASE_PACKET_LEN) @@ -534,6 +542,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): if exts is None or not isinstance(exts, list) or len(exts) == 0: self._debug('Protocol error - bogus accepted extensions') self.device_socket.close() + self.is_connected = False return False self.FORMATS = exts if password: @@ -542,20 +551,24 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): # protocol mismatch self._debug('Protocol error - missing password hash') self.device_socket.close() + self.is_connected = False return False if returned_hash != hash_digest: # bad password self._debug('password mismatch') self._call_client("DISPLAY_MESSAGE", {'messageKind':1}) + self.is_connected = False self.device_socket.close() return False return True except socket.timeout: self.device_socket.close() + self.is_connected = False except socket.error: x = sys.exc_info()[1] self._debug('unexpected socket exception', x.args[0]) self.device_socket.close() + self.is_connected = False raise return False @@ -811,6 +824,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): self._debug('Failed to allocate a port'); self.listen_socket.close() self.listen_socket = None + self.is_connected = False return try: @@ -819,6 +833,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): self._debug('listen on socket failed', port) self.listen_socket.close() self.listen_socket = None + self.is_connected = False return try: @@ -827,6 +842,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): self._debug('registration with bonjour failed') self.listen_socket.close() self.listen_socket = None + self.is_connected = False return self._debug('listening on port', port) @@ -838,6 +854,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): do_zeroconf(unpublish_zeroconf, self.port) self.listen_socket.close() self.listen_socket = None + self.is_connected = False # Methods for dynamic control From 36213138959c9511b0408004fcaf072294e69319 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Wed, 1 Aug 2012 21:34:43 +0200 Subject: [PATCH 4/5] ... --- src/calibre/devices/smart_device_app/driver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/devices/smart_device_app/driver.py b/src/calibre/devices/smart_device_app/driver.py index 9d520abd99..8277ab7a2d 100644 --- a/src/calibre/devices/smart_device_app/driver.py +++ b/src/calibre/devices/smart_device_app/driver.py @@ -14,6 +14,7 @@ from functools import wraps from calibre import prints from calibre.constants import numeric_version, DEBUG +from calibre.devices.errors import OpenFeedback from calibre.devices.interface import DevicePlugin from calibre.devices.usbms.books import Book, BookList from calibre.devices.usbms.deviceconfig import DeviceConfig @@ -559,7 +560,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): self._call_client("DISPLAY_MESSAGE", {'messageKind':1}) self.is_connected = False self.device_socket.close() - return False + raise OpenFeedback('Incorrect password supplied') return True except socket.timeout: self.device_socket.close() From 7c38dabc8bd9cebc089042e746e28ea549337347 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Thu, 2 Aug 2012 09:34:28 +0200 Subject: [PATCH 5/5] Change color of dot on smartdevice menu. Add a port option to the smartdevice. --- .../devices/smart_device_app/driver.py | 23 ++++++++++++++++--- src/calibre/gui2/actions/device.py | 9 ++++++-- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/calibre/devices/smart_device_app/driver.py b/src/calibre/devices/smart_device_app/driver.py index 8277ab7a2d..046ae1a05a 100644 --- a/src/calibre/devices/smart_device_app/driver.py +++ b/src/calibre/devices/smart_device_app/driver.py @@ -113,6 +113,11 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): _('Security password') + ':::
' + _('Enter a password that the device app must use to connect to calibre') + '
', '', + _('Use fixed network port') + ':::' + + _('If checked, use the port number in the "Port" box, otherwise ' + 'the driver will pick a random port') + '
', + _('Port') + ':::' + + _('Enter the port number the driver is to use if the "fixed port" box is checked') + '
', _('Print extra debug information') + ':::' + _('Check this box if requested when reporting problems') + '
', ] @@ -121,11 +126,14 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): '', '', '', + False, '9090', False, ] OPT_AUTOSTART = 0 OPT_PASSWORD = 2 - OPT_EXTRA_DEBUG = 4 + OPT_USE_PORT = 4 + OPT_PORT_NUMBER = 5 + OPT_EXTRA_DEBUG = 6 def __init__(self, path): self.sync_lock = threading.RLock() @@ -809,8 +817,17 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): self._debug('creation of listen socket failed') return - for i in xrange(100): # try up to 100 random port numbers - port = random.randint(8192, 32000) + i = 0 + while i < 100: # try up to 100 random port numbers + if self.settings().extra_customization[self.OPT_USE_PORT]: + i = 100 + try: + port = int(self.settings().extra_customization[self.OPT_PORT_NUMBER]) + except: + port = 0 + else: + i += 1 + port = random.randint(8192, 32000) try: self._debug('try port', port) self.listen_socket.bind(('', port)) diff --git a/src/calibre/gui2/actions/device.py b/src/calibre/gui2/actions/device.py index d9b3dadba7..1151d1976a 100644 --- a/src/calibre/gui2/actions/device.py +++ b/src/calibre/gui2/actions/device.py @@ -237,9 +237,14 @@ class ConnectShareAction(InterfaceAction): self.share_conn_menu.hide_smartdevice_menus() def set_smartdevice_action_state(self): + from calibre.utils.mdns import get_external_ip running = self.gui.device_manager.is_running('smartdevice') + if not running: + text = self.share_conn_menu.DEVICE_MSGS[0] + else: + text = self.share_conn_menu.DEVICE_MSGS[1] + ' [%s]'%get_external_ip() + icon = 'green' if running else 'red' ac = self.share_conn_menu.control_smartdevice_action - text, icon = (1, 'green') if running else (0, 'red') ac.setIcon(QIcon(I('dot_%s.png'%icon))) - ac.setText(self.share_conn_menu.DEVICE_MSGS[text]) + ac.setText(text)