Wireless device connections: Add an option to force calibre ot listen on a particular IP address. Access it by customizing the plugin in Preferences->Plugins

This commit is contained in:
Kovid Goyal 2012-09-05 17:47:21 +05:30
commit fc0b2732a6
4 changed files with 59 additions and 29 deletions

View File

@ -33,7 +33,7 @@ from calibre.utils.config import from_json, tweaks
from calibre.utils.date import isoformat, now from calibre.utils.date import isoformat, now
from calibre.utils.filenames import ascii_filename as sanitize, shorten_components_to from calibre.utils.filenames import ascii_filename as sanitize, shorten_components_to
from calibre.utils.mdns import (publish as publish_zeroconf, unpublish as from calibre.utils.mdns import (publish as publish_zeroconf, unpublish as
unpublish_zeroconf) unpublish_zeroconf, get_all_ips)
def synchronous(tlockname): def synchronous(tlockname):
"""A decorator to place an instance based lock around a method """ """A decorator to place an instance based lock around a method """
@ -46,10 +46,6 @@ def synchronous(tlockname):
return _synchronizer return _synchronizer
return _synched return _synched
def do_zeroconf(f, port):
f('calibre smart device client',
'_calibresmartdeviceapp._tcp', port, {})
class SDBook(Book): class SDBook(Book):
def __init__(self, prefix, lpath, size=None, other=None): def __init__(self, prefix, lpath, size=None, other=None):
@ -80,7 +76,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
CAN_DO_DEVICE_DB_PLUGBOARD = False CAN_DO_DEVICE_DB_PLUGBOARD = False
SUPPORTS_SUB_DIRS = True SUPPORTS_SUB_DIRS = True
MUST_READ_METADATA = True MUST_READ_METADATA = True
NEWS_IN_FOLDER = False NEWS_IN_FOLDER = True
SUPPORTS_USE_AUTHOR_SORT = False SUPPORTS_USE_AUTHOR_SORT = False
WANTS_UPDATED_THUMBNAILS = True WANTS_UPDATED_THUMBNAILS = True
MAX_PATH_LEN = 250 MAX_PATH_LEN = 250
@ -97,7 +93,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
SEND_NOOP_EVERY_NTH_PROBE = 5 SEND_NOOP_EVERY_NTH_PROBE = 5
DISCONNECT_AFTER_N_SECONDS = 30*60 # 30 minutes DISCONNECT_AFTER_N_SECONDS = 30*60 # 30 minutes
ZEROCONF_CLIENT_STRING = b'calibre smart device client' ZEROCONF_CLIENT_STRING = b'calibre wireless device client'
# A few "random" port numbers to use for detecting clients using broadcast # A few "random" port numbers to use for detecting clients using broadcast
# The clients are expected to broadcast a UDP 'hi there' on all of these # The clients are expected to broadcast a UDP 'hi there' on all of these
@ -130,8 +126,9 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
} }
reverse_opcodes = dict([(v, k) for k,v in opcodes.iteritems()]) reverse_opcodes = dict([(v, k) for k,v in opcodes.iteritems()])
ALL_BY_TITLE = _('All by title') ALL_BY_TITLE = _('All by title')
ALL_BY_AUTHOR = _('All by author') ALL_BY_AUTHOR = _('All by author')
ALL_BY_SOMETHING = _('All by something')
EXTRA_CUSTOMIZATION_MESSAGE = [ EXTRA_CUSTOMIZATION_MESSAGE = [
_('Enable connections at startup') + ':::<p>' + _('Enable connections at startup') + ':::<p>' +
@ -149,18 +146,25 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
_('Check this box if requested when reporting problems') + '</p>', _('Check this box if requested when reporting problems') + '</p>',
'', '',
_('Comma separated list of metadata fields ' _('Comma separated list of metadata fields '
'to turn into collections on the device. Possibilities include: ')+\ 'to turn into collections on the device.') + ':::<p>' +
'series, tags, authors' +\ _('Possibilities include: series, tags, authors, etc' +
_('. Two special collections are available: %(abt)s:%(abtv)s and %(aba)s:%(abav)s. Add ' '. Three special collections are available: %(abt)s:%(abtv)s, '
'these values to the list to enable them. The collections will be ' '%(aba)s:%(abav)s, and %(abs)s:%(absv)s. Add '
'given the name provided after the ":" character.')%dict( 'these values to the list to enable them. The collections will be '
abt='abt', abtv=ALL_BY_TITLE, aba='aba', abav=ALL_BY_AUTHOR), 'given the name provided after the ":" character.')%dict(
abt='abt', abtv=ALL_BY_TITLE, aba='aba', abav=ALL_BY_AUTHOR,
abs='abs', absv=ALL_BY_SOMETHING),
'', '',
_('Enable the no-activity timeout') + ':::<p>' + _('Enable the no-activity timeout') + ':::<p>' +
_('If this box is checked, calibre will automatically disconnect if ' _('If this box is checked, calibre will automatically disconnect if '
'a connected device does nothing for %d minutes. Unchecking this ' 'a connected device does nothing for %d minutes. Unchecking this '
' box disables this timeout, so calibre will never automatically ' ' box disables this timeout, so calibre will never automatically '
'disconnect.')%(DISCONNECT_AFTER_N_SECONDS/60,) + '</p>', 'disconnect.')%(DISCONNECT_AFTER_N_SECONDS/60,) + '</p>',
_('Use this IP address') + ':::<p>' +
_('Use this option if you want to force the driver to listen on a '
'particular IP address. The driver will listen only on the '
'entered address, and this address will be the one advertized '
'over mDNS (bonjour).') + '</p>',
] ]
EXTRA_CUSTOMIZATION_DEFAULT = [ EXTRA_CUSTOMIZATION_DEFAULT = [
False, False,
@ -173,6 +177,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
'', '',
'', '',
True, True,
''
] ]
OPT_AUTOSTART = 0 OPT_AUTOSTART = 0
OPT_PASSWORD = 2 OPT_PASSWORD = 2
@ -181,6 +186,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
OPT_EXTRA_DEBUG = 6 OPT_EXTRA_DEBUG = 6
OPT_COLLECTIONS = 8 OPT_COLLECTIONS = 8
OPT_AUTODISCONNECT = 10 OPT_AUTODISCONNECT = 10
OPT_FORCE_IP_ADDRESS = 11
def __init__(self, path): def __init__(self, path):
@ -499,6 +505,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
return self.OPT_USE_PORT return self.OPT_USE_PORT
elif opt_string == 'port_number': elif opt_string == 'port_number':
return self.OPT_PORT_NUMBER return self.OPT_PORT_NUMBER
elif opt_string == 'force_ip_address':
return self.OPT_FORCE_IP_ADDRESS
else: else:
return None return None
@ -527,8 +535,12 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
def _attach_to_port(self, sock, port): def _attach_to_port(self, sock, port):
try: try:
self._debug('try port', port) ip_addr = self.settings().extra_customization[self.OPT_FORCE_IP_ADDRESS]
sock.bind(('', port)) self._debug('try ip address "'+ ip_addr + '"', 'on port', port)
if ip_addr:
sock.bind((ip_addr, port))
else:
sock.bind(('', port))
except socket.error: except socket.error:
self._debug('socket error on port', port) self._debug('socket error on port', port)
port = 0 port = 0
@ -996,6 +1008,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
self.client_can_stream_books = False self.client_can_stream_books = False
self.client_can_stream_metadata = False self.client_can_stream_metadata = False
self._debug("All IP addresses", get_all_ips())
message = None message = None
try: try:
self.listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@ -1044,7 +1058,10 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
return message return message
try: try:
do_zeroconf(publish_zeroconf, port) ip_addr = self.settings().extra_customization[self.OPT_FORCE_IP_ADDRESS]
publish_zeroconf('calibre smart device client',
'_calibresmartdeviceapp._tcp', port, {},
use_ip_address=ip_addr)
except: except:
message = 'registration with bonjour failed' message = 'registration with bonjour failed'
self._debug(message) self._debug(message)
@ -1080,7 +1097,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
@synchronous('sync_lock') @synchronous('sync_lock')
def shutdown(self): def shutdown(self):
if getattr(self, 'listen_socket', None) is not None: if getattr(self, 'listen_socket', None) is not None:
do_zeroconf(unpublish_zeroconf, self.port) unpublish_zeroconf('calibre smart device client',
'_calibresmartdeviceapp._tcp', self.port, {})
self._close_listen_socket() self._close_listen_socket()
# Methods for dynamic control # Methods for dynamic control

View File

@ -240,13 +240,18 @@ class ConnectShareAction(InterfaceAction):
from calibre.gui2.dialogs.smartdevice import get_all_ip_addresses from calibre.gui2.dialogs.smartdevice import get_all_ip_addresses
dm = self.gui.device_manager dm = self.gui.device_manager
all_ips = get_all_ip_addresses() forced_ip = dm.get_option('smartdevice', 'force_ip_address')
if len(all_ips) > 3: if forced_ip:
formatted_addresses = _('Many IP addresses. See Start/Stop dialog.') formatted_addresses = forced_ip
show_port = False
else:
formatted_addresses = ' or '.join(get_all_ip_addresses())
show_port = True show_port = True
else:
all_ips = get_all_ip_addresses()
if len(all_ips) > 3:
formatted_addresses = _('Many IP addresses. See Start/Stop dialog.')
show_port = False
else:
formatted_addresses = ' or '.join(get_all_ip_addresses())
show_port = True
running = dm.is_running('smartdevice') running = dm.is_running('smartdevice')
if not running: if not running:

View File

@ -115,7 +115,11 @@ class SmartdeviceDialog(QDialog, Ui_Dialog):
self.auto_mgmt_button.setText(_('Automatic metadata management is enabled')) self.auto_mgmt_button.setText(_('Automatic metadata management is enabled'))
self.auto_mgmt_button.setEnabled(False) self.auto_mgmt_button.setEnabled(False)
self.ip_addresses.setText(', '.join(get_all_ip_addresses())) forced_ip = self.device_manager.get_option('smartdevice', 'force_ip_address')
if forced_ip:
self.ip_addresses.setText(forced_ip)
else:
self.ip_addresses.setText(', '.join(get_all_ip_addresses()))
self.resize(self.sizeHint()) self.resize(self.sizeHint())

View File

@ -60,7 +60,7 @@ def start_server():
return _server return _server
def create_service(desc, type, port, properties, add_hostname): def create_service(desc, type, port, properties, add_hostname, use_ip_address=None):
port = int(port) port = int(port)
try: try:
hostname = socket.gethostname().partition('.')[0] hostname = socket.gethostname().partition('.')[0]
@ -69,7 +69,10 @@ def create_service(desc, type, port, properties, add_hostname):
if add_hostname: if add_hostname:
desc += ' (on %s)'%hostname desc += ' (on %s)'%hostname
local_ip = get_external_ip() if use_ip_address:
local_ip = use_ip_address
else:
local_ip = get_external_ip()
type = type+'.local.' type = type+'.local.'
from calibre.utils.Zeroconf import ServiceInfo from calibre.utils.Zeroconf import ServiceInfo
return ServiceInfo(type, desc+'.'+type, return ServiceInfo(type, desc+'.'+type,
@ -79,7 +82,7 @@ def create_service(desc, type, port, properties, add_hostname):
server=hostname+'.local.') server=hostname+'.local.')
def publish(desc, type, port, properties=None, add_hostname=True): def publish(desc, type, port, properties=None, add_hostname=True, use_ip_address=None):
''' '''
Publish a service. Publish a service.