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.filenames import ascii_filename as sanitize, shorten_components_to
from calibre.utils.mdns import (publish as publish_zeroconf, unpublish as
unpublish_zeroconf)
unpublish_zeroconf, get_all_ips)
def synchronous(tlockname):
"""A decorator to place an instance based lock around a method """
@ -46,10 +46,6 @@ def synchronous(tlockname):
return _synchronizer
return _synched
def do_zeroconf(f, port):
f('calibre smart device client',
'_calibresmartdeviceapp._tcp', port, {})
class SDBook(Book):
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
SUPPORTS_SUB_DIRS = True
MUST_READ_METADATA = True
NEWS_IN_FOLDER = False
NEWS_IN_FOLDER = True
SUPPORTS_USE_AUTHOR_SORT = False
WANTS_UPDATED_THUMBNAILS = True
MAX_PATH_LEN = 250
@ -97,7 +93,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
SEND_NOOP_EVERY_NTH_PROBE = 5
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
# 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()])
ALL_BY_TITLE = _('All by title')
ALL_BY_AUTHOR = _('All by author')
ALL_BY_TITLE = _('All by title')
ALL_BY_AUTHOR = _('All by author')
ALL_BY_SOMETHING = _('All by something')
EXTRA_CUSTOMIZATION_MESSAGE = [
_('Enable connections at startup') + ':::<p>' +
@ -149,18 +146,25 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
_('Check this box if requested when reporting problems') + '</p>',
'',
_('Comma separated list of metadata fields '
'to turn into collections on the device. Possibilities include: ')+\
'series, tags, authors' +\
_('. 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 '
'given the name provided after the ":" character.')%dict(
abt='abt', abtv=ALL_BY_TITLE, aba='aba', abav=ALL_BY_AUTHOR),
'to turn into collections on the device.') + ':::<p>' +
_('Possibilities include: series, tags, authors, etc' +
'. Three special collections are available: %(abt)s:%(abtv)s, '
'%(aba)s:%(abav)s, and %(abs)s:%(absv)s. Add '
'these values to the list to enable them. The collections will be '
'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>' +
_('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>',
_('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 = [
False,
@ -173,6 +177,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
'',
'',
True,
''
]
OPT_AUTOSTART = 0
OPT_PASSWORD = 2
@ -181,6 +186,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
OPT_EXTRA_DEBUG = 6
OPT_COLLECTIONS = 8
OPT_AUTODISCONNECT = 10
OPT_FORCE_IP_ADDRESS = 11
def __init__(self, path):
@ -499,6 +505,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
return self.OPT_USE_PORT
elif opt_string == 'port_number':
return self.OPT_PORT_NUMBER
elif opt_string == 'force_ip_address':
return self.OPT_FORCE_IP_ADDRESS
else:
return None
@ -527,8 +535,12 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
def _attach_to_port(self, sock, port):
try:
self._debug('try port', port)
sock.bind(('', port))
ip_addr = self.settings().extra_customization[self.OPT_FORCE_IP_ADDRESS]
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:
self._debug('socket error on port', port)
port = 0
@ -996,6 +1008,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
self.client_can_stream_books = False
self.client_can_stream_metadata = False
self._debug("All IP addresses", get_all_ips())
message = None
try:
self.listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@ -1044,7 +1058,10 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
return message
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:
message = 'registration with bonjour failed'
self._debug(message)
@ -1080,7 +1097,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
@synchronous('sync_lock')
def shutdown(self):
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()
# Methods for dynamic control

View File

@ -240,13 +240,18 @@ class ConnectShareAction(InterfaceAction):
from calibre.gui2.dialogs.smartdevice import get_all_ip_addresses
dm = self.gui.device_manager
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())
forced_ip = dm.get_option('smartdevice', 'force_ip_address')
if forced_ip:
formatted_addresses = forced_ip
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')
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.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())

View File

@ -60,7 +60,7 @@ def start_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)
try:
hostname = socket.gethostname().partition('.')[0]
@ -69,7 +69,10 @@ def create_service(desc, type, port, properties, add_hostname):
if add_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.'
from calibre.utils.Zeroconf import ServiceInfo
return ServiceInfo(type, desc+'.'+type,
@ -79,7 +82,7 @@ def create_service(desc, type, port, properties, add_hostname):
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.