From dd1c304626e57183060dbcaf26865be3f533c530 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 4 Jun 2015 08:04:33 +0530 Subject: [PATCH] Implement BonJour for the new server --- src/calibre/srv/opts.py | 5 ++++ src/calibre/srv/plugins.py | 51 +++++++++++++++++++++++++++++++++++ src/calibre/srv/standalone.py | 6 ++++- src/calibre/srv/tests/loop.py | 17 ++++++++++++ 4 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 src/calibre/srv/plugins.py diff --git a/src/calibre/srv/opts.py b/src/calibre/srv/opts.py index 27329fa04f..05a2a1cd5b 100644 --- a/src/calibre/srv/opts.py +++ b/src/calibre/srv/opts.py @@ -65,6 +65,11 @@ raw_options = ( 'url_prefix', None, 'Useful if you wish to run this server behind a reverse proxy.', + 'Advertise OPDS feeds via BonJour', + 'use_bonjour', True, + 'Advertise the OPDS feeds via the BonJour service, so that OPDS based' + ' reading apps can detect and connect to the server automatically.', + 'The interface on which to listen for connections', 'listen_on', '0.0.0.0', 'The default is to listen on all available interfaces. You can change this to, for' diff --git a/src/calibre/srv/plugins.py b/src/calibre/srv/plugins.py new file mode 100644 index 0000000000..92b448736f --- /dev/null +++ b/src/calibre/srv/plugins.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python2 +# vim:fileencoding=utf-8 +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2015, Kovid Goyal ' + +from threading import Event + +class BonJour(object): # {{{ + + def __init__(self, name='Books in calibre', service_type='_calibre._tcp', path='/opds', add_hostname=True): + self.service_name = name + self.service_type = service_type + self.add_hostname = add_hostname + self.path = path + self.shutdown = Event() + self.stop = self.shutdown.set + self.started = Event() + self.stopped = Event() + self.services = [] + + def start(self, loop): + from calibre.utils.mdns import publish, unpublish, get_external_ip, verify_ipV4_address + ip_address, port = loop.bound_address[:2] + self.zeroconf_ip_address = zipa = verify_ipV4_address(ip_address) or get_external_ip() + prefix = loop.opts.url_prefix or '' + # The Zeroconf module requires everything to be bytestrings + def enc(x): + if not isinstance(x, bytes): + x = x.encode('ascii') + return x + mdns_services = ( + (enc(self.service_name), enc(self.service_type), port, {b'path':enc(prefix + self.path)}), + ) + if self.shutdown.is_set(): + return + self.services = [] + + for s in mdns_services: + self.services.append(publish(*s, use_ip_address=zipa, add_hostname=self.add_hostname)) + loop.log('OPDS feeds advertised via BonJour at: %s port: %s' % (zipa, port)) + self.advertised_port = port + self.started.set() + + self.shutdown.wait() + for s in mdns_services: + unpublish(*s, add_hostname=self.add_hostname) + self.stopped.set() +# }}} diff --git a/src/calibre/srv/standalone.py b/src/calibre/srv/standalone.py index 3d81451903..4cc8063483 100644 --- a/src/calibre/srv/standalone.py +++ b/src/calibre/srv/standalone.py @@ -11,6 +11,7 @@ import sys, os from calibre import as_unicode from calibre.constants import plugins, iswindows from calibre.srv.loop import ServerLoop +from calibre.srv.plugins import BonJour from calibre.srv.opts import opts_to_parser from calibre.srv.http_response import create_http_handler from calibre.srv.handler import Handler @@ -60,7 +61,10 @@ class Server(object): if opts.log: log = RotatingLog(opts.log, max_size=opts.max_log_size) self.handler = Handler(libraries, opts) - self.loop = ServerLoop(create_http_handler(self.handler.dispatch), opts=opts, log=log) + plugins = [] + if opts.use_bonjour: + plugins.append(BonJour()) + self.loop = ServerLoop(create_http_handler(self.handler.dispatch), opts=opts, log=log, plugins=plugins) self.handler.set_log(self.loop.log) self.serve_forever = self.loop.serve_forever diff --git a/src/calibre/srv/tests/loop.py b/src/calibre/srv/tests/loop.py index 3fa07466f0..e0475babd0 100644 --- a/src/calibre/srv/tests/loop.py +++ b/src/calibre/srv/tests/loop.py @@ -101,6 +101,23 @@ class LoopTest(BaseTest): with TestServer(lambda data:(data.path[0] + data.read()), listen_on='1.1.1.1', fallback_to_detected_interface=True, specialize=specialize) as server: self.assertNotEqual('1.1.1.1', server.address[0]) + def test_bonjour(self): + 'Test advertising via BonJour' + from calibre.srv.plugins import BonJour + from calibre.utils.Zeroconf import Zeroconf + b = BonJour() + with TestServer(lambda data:(data.path[0] + data.read()), plugins=(b,), shutdown_timeout=5) as server: + self.assertTrue(b.started.wait(5), 'BonJour not started') + self.ae(b.advertised_port, server.address[1]) + service = b.services[0] + self.ae(service.type, b'_calibre._tcp.local.') + r = Zeroconf() + info = r.getServiceInfo(service.type, service.name) + self.assertIsNotNone(info) + self.ae(info.text, b'\npath=/opds') + + self.assertTrue(b.stopped.wait(5), 'BonJour not stopped') + def test_ring_buffer(self): 'Test the ring buffer used for reads' class FakeSocket(object):