Fix problem with ControlerError messages getting lost. Add a no-activity timer to SmartDevice.

This commit is contained in:
Charles Haley 2012-08-12 10:33:44 +02:00
parent df65db13d1
commit 30627e5e0c
2 changed files with 35 additions and 16 deletions

View File

@ -92,6 +92,7 @@ class ControlError(ProtocolError):
def __init__(self, query=None, response=None, desc=None): def __init__(self, query=None, response=None, desc=None):
self.query = query self.query = query
self.response = response self.response = response
self.desc = desc
ProtocolError.__init__(self, desc) ProtocolError.__init__(self, desc)
def __str__(self): def __str__(self):

View File

@ -85,6 +85,9 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
MAX_CLIENT_COMM_TIMEOUT = 60.0 # Wait at most N seconds for an answer MAX_CLIENT_COMM_TIMEOUT = 60.0 # Wait at most N seconds for an answer
MAX_UNSUCCESSFUL_CONNECTS = 5 MAX_UNSUCCESSFUL_CONNECTS = 5
SEND_NOOP_EVERY_NTH_PROBE = 5
DISCONNECT_AFTER_N_SECONDS = 30*60 # 30 minutes
opcodes = { opcodes = {
'NOOP' : 12, 'NOOP' : 12,
'OK' : 0, 'OK' : 0,
@ -120,7 +123,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
_('Use fixed network port') + ':::<p>' + _('Use fixed network port') + ':::<p>' +
_('If checked, use the port number in the "Port" box, otherwise ' _('If checked, use the port number in the "Port" box, otherwise '
'the driver will pick a random port') + '</p>', 'the driver will pick a random port') + '</p>',
_('Port') + ':::<p>' + _('Port number: ') + ':::<p>' +
_('Enter the port number the driver is to use if the "fixed port" box is checked') + '</p>', _('Enter the port number the driver is to use if the "fixed port" box is checked') + '</p>',
_('Print extra debug information') + ':::<p>' + _('Print extra debug information') + ':::<p>' +
_('Check this box if requested when reporting problems') + '</p>', _('Check this box if requested when reporting problems') + '</p>',
@ -131,7 +134,13 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
_('. Two special collections are available: %(abt)s:%(abtv)s and %(aba)s:%(abav)s. Add ' _('. Two special collections are available: %(abt)s:%(abtv)s and %(aba)s:%(abav)s. Add '
'these values to the list to enable them. The collections will be ' 'these values to the list to enable them. The collections will be '
'given the name provided after the ":" character.')%dict( 'given the name provided after the ":" character.')%dict(
abt='abt', abtv=ALL_BY_TITLE, aba='aba', abav=ALL_BY_AUTHOR) abt='abt', abtv=ALL_BY_TITLE, aba='aba', abav=ALL_BY_AUTHOR),
'',
_('Enable the no-activity timeout') + ':::<p>' +
_('If this box is checked, calibre will automatically disconnect if '
'a connected device does nothing for %d minutes. Unchecking this '
' box disables this timeout, so calibre will never automatically '
'disconnect.')%(DISCONNECT_AFTER_N_SECONDS/60,) + '</p>',
] ]
EXTRA_CUSTOMIZATION_DEFAULT = [ EXTRA_CUSTOMIZATION_DEFAULT = [
False, False,
@ -141,7 +150,9 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
False, '9090', False, '9090',
False, False,
'', '',
'' '',
'',
True,
] ]
OPT_AUTOSTART = 0 OPT_AUTOSTART = 0
OPT_PASSWORD = 2 OPT_PASSWORD = 2
@ -149,6 +160,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
OPT_PORT_NUMBER = 5 OPT_PORT_NUMBER = 5
OPT_EXTRA_DEBUG = 6 OPT_EXTRA_DEBUG = 6
OPT_COLLECTIONS = 8 OPT_COLLECTIONS = 8
OPT_AUTODISCONNECT = 10
def __init__(self, path): def __init__(self, path):
self.sync_lock = threading.RLock() self.sync_lock = threading.RLock()
@ -373,13 +385,13 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
except socket.error: except socket.error:
self._debug('device went away') self._debug('device went away')
self._close_device_socket() self._close_device_socket()
raise ControlError('Device closed the network connection') raise ControlError(desc='Device closed the network connection')
except: except:
self._debug('other exception') self._debug('other exception')
traceback.print_exc() traceback.print_exc()
self._close_device_socket() self._close_device_socket()
raise raise
raise ControlError('Device responded with incorrect information') raise ControlError(desc='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):
@ -475,7 +487,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
self.is_connected = False self.is_connected = False
if self.is_connected: if self.is_connected:
self.noop_counter += 1 self.noop_counter += 1
if only_presence and (self.noop_counter % 5) != 1: if only_presence and (
self.noop_counter % self.SEND_NOOP_EVERY_NTH_PROBE) != 1:
try: try:
ans = select.select((self.device_socket,), (), (), 0) ans = select.select((self.device_socket,), (), (), 0)
if len(ans[0]) == 0: if len(ans[0]) == 0:
@ -486,11 +499,16 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
# This will usually toss an exception if the socket is gone. # This will usually toss an exception if the socket is gone.
except: except:
pass pass
try: if (self.settings().extra_customization[self.OPT_AUTODISCONNECT] and
if self._call_client('NOOP', dict())[0] is None: self.noop_counter > self.DISCONNECT_AFTER_N_SECONDS):
self._close_device_socket()
except:
self._close_device_socket() self._close_device_socket()
self._debug('timeout -- disconnected')
else:
try:
if self._call_client('NOOP', dict())[0] is None:
self._close_device_socket()
except:
self._close_device_socket()
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)
@ -533,7 +551,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
self._debug() self._debug()
if not self.is_connected: if not self.is_connected:
# We have been called to retry the connection. Give up immediately # We have been called to retry the connection. Give up immediately
raise ControlError('Attempt to open a closed device') raise ControlError(desc='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:
@ -689,7 +707,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 ControlError('book metadata not returned') raise ControlError(desc='book metadata not returned')
return bl return bl
@synchronous('sync_lock') @synchronous('sync_lock')
@ -720,7 +738,7 @@ 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 ControlError('sync_booklists') raise ControlError(desc='sync_booklists')
@synchronous('sync_lock') @synchronous('sync_lock')
def eject(self): def eject(self):
@ -748,7 +766,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 ControlError('Sending book %s to device failed' % lpath) raise ControlError(desc='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
@ -789,7 +807,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 ControlError('Protocol error - delete books') raise ControlError(desc='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):
@ -825,7 +843,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
else: else:
eof = True eof = True
else: else:
raise ControlError('request for book data failed') raise ControlError(desc='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):