mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-06-23 15:30:45 -04:00
Wireless device driver: Fix errors on some OS X machines caused by network resource exhaustion.
This commit is contained in:
commit
b71829038e
@ -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):
|
||||||
|
@ -11,11 +11,12 @@ import socket, select, json, inspect, os, traceback, time, sys, random
|
|||||||
import hashlib, threading
|
import hashlib, threading
|
||||||
from base64 import b64encode, b64decode
|
from base64 import b64encode, b64decode
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
from errno import EAGAIN, EINTR
|
||||||
|
|
||||||
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 (OpenFailed, ControlError, TimeoutError,
|
from calibre.devices.errors import (OpenFailed, ControlError, TimeoutError,
|
||||||
InitialConnectionError)
|
InitialConnectionError, PacketError)
|
||||||
from calibre.devices.interface import DevicePlugin
|
from calibre.devices.interface import DevicePlugin
|
||||||
from calibre.devices.usbms.books import Book, CollectionsBookList
|
from calibre.devices.usbms.books import Book, CollectionsBookList
|
||||||
from calibre.devices.usbms.deviceconfig import DeviceConfig
|
from calibre.devices.usbms.deviceconfig import DeviceConfig
|
||||||
@ -85,6 +86,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 +124,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 +135,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 +151,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 +161,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()
|
||||||
@ -165,6 +178,15 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
inspect.stack()[1][3]), end='')
|
inspect.stack()[1][3]), end='')
|
||||||
for a in args:
|
for a in args:
|
||||||
try:
|
try:
|
||||||
|
if isinstance(a, dict):
|
||||||
|
printable = {}
|
||||||
|
for k,v in a.iteritems():
|
||||||
|
if isinstance(v, (str, unicode)) and len(v) > 50:
|
||||||
|
printable[k] = 'too long'
|
||||||
|
else:
|
||||||
|
printable[k] = v
|
||||||
|
prints('', printable, end='');
|
||||||
|
else:
|
||||||
prints('', a, end='')
|
prints('', a, end='')
|
||||||
except:
|
except:
|
||||||
prints('', 'value too long', end='')
|
prints('', 'value too long', end='')
|
||||||
@ -339,6 +361,26 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
pos += len(v)
|
pos += len(v)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
def _send_byte_string(self, s):
|
||||||
|
if not isinstance(s, bytes):
|
||||||
|
self._debug('given a non-byte string!')
|
||||||
|
raise PacketError("Internal error: found a string that isn't bytes")
|
||||||
|
sent_len = 0;
|
||||||
|
total_len = len(s)
|
||||||
|
while sent_len < total_len:
|
||||||
|
try:
|
||||||
|
if sent_len == 0:
|
||||||
|
amt_sent = self.device_socket.send(s)
|
||||||
|
else:
|
||||||
|
amt_sent = self.device_socket.send(s[sent_len:])
|
||||||
|
if amt_sent <= 0:
|
||||||
|
raise IOError('Bad write on device socket');
|
||||||
|
sent_len += amt_sent
|
||||||
|
except socket.error as e:
|
||||||
|
self._debug('socket error', e, e.errno)
|
||||||
|
if e.args[0] != EAGAIN and e.args[0] != EINTR:
|
||||||
|
raise
|
||||||
|
|
||||||
def _call_client(self, op, arg, print_debug_info=True):
|
def _call_client(self, op, arg, print_debug_info=True):
|
||||||
if op != 'NOOP':
|
if op != 'NOOP':
|
||||||
self.noop_counter = 0
|
self.noop_counter = 0
|
||||||
@ -355,9 +397,9 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
if print_debug_info and extra_debug:
|
if print_debug_info and extra_debug:
|
||||||
self._debug('send string', s)
|
self._debug('send string', s)
|
||||||
self.device_socket.settimeout(self.MAX_CLIENT_COMM_TIMEOUT)
|
self.device_socket.settimeout(self.MAX_CLIENT_COMM_TIMEOUT)
|
||||||
self.device_socket.sendall(('%d' % len(s))+s)
|
self._send_byte_string((b'%d' % len(s))+s)
|
||||||
self.device_socket.settimeout(None)
|
|
||||||
v = self._read_string_from_net()
|
v = self._read_string_from_net()
|
||||||
|
self.device_socket.settimeout(None)
|
||||||
if print_debug_info and extra_debug:
|
if print_debug_info and extra_debug:
|
||||||
self._debug('received string', v)
|
self._debug('received string', v)
|
||||||
if v:
|
if v:
|
||||||
@ -373,13 +415,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 +517,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,6 +529,11 @@ 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
|
||||||
|
if (self.settings().extra_customization[self.OPT_AUTODISCONNECT] and
|
||||||
|
self.noop_counter > self.DISCONNECT_AFTER_N_SECONDS):
|
||||||
|
self._close_device_socket()
|
||||||
|
self._debug('timeout -- disconnected')
|
||||||
|
else:
|
||||||
try:
|
try:
|
||||||
if self._call_client('NOOP', dict())[0] is None:
|
if self._call_client('NOOP', dict())[0] is None:
|
||||||
self._close_device_socket()
|
self._close_device_socket()
|
||||||
@ -533,7 +581,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:
|
||||||
@ -569,6 +617,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
self._debug('Protocol error - bogus book packet length')
|
self._debug('Protocol error - bogus book packet length')
|
||||||
self._close_device_socket()
|
self._close_device_socket()
|
||||||
return False
|
return False
|
||||||
|
self._debug('CC version #:', result.get('ccVersionNumber', 'unknown'))
|
||||||
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)
|
||||||
@ -689,7 +738,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 +769,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 +797,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 +838,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 +874,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):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user