diff --git a/src/calibre/devices/smart_device_app/driver.py b/src/calibre/devices/smart_device_app/driver.py index 2d2ddaf568..b1cd1e635b 100644 --- a/src/calibre/devices/smart_device_app/driver.py +++ b/src/calibre/devices/smart_device_app/driver.py @@ -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') + ':::
' + @@ -149,18 +146,25 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): _('Check this box if requested when reporting problems') + '
', '', _('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.') + ':::' + + _('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') + ':::
' + _('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,) + '
', + _('Use this IP address') + ':::' + + _('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).') + '
', ] 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 diff --git a/src/calibre/gui2/actions/device.py b/src/calibre/gui2/actions/device.py index a8475c3a3e..6e215661c8 100644 --- a/src/calibre/gui2/actions/device.py +++ b/src/calibre/gui2/actions/device.py @@ -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: diff --git a/src/calibre/gui2/dialogs/smartdevice.py b/src/calibre/gui2/dialogs/smartdevice.py index ba58b71048..745125d409 100644 --- a/src/calibre/gui2/dialogs/smartdevice.py +++ b/src/calibre/gui2/dialogs/smartdevice.py @@ -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()) diff --git a/src/calibre/utils/mdns.py b/src/calibre/utils/mdns.py index 6140435e46..abbd6c2247 100644 --- a/src/calibre/utils/mdns.py +++ b/src/calibre/utils/mdns.py @@ -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.