Implement BonJour for the new server

This commit is contained in:
Kovid Goyal 2015-06-04 08:04:33 +05:30
parent 24a392e9de
commit dd1c304626
4 changed files with 78 additions and 1 deletions

View File

@ -65,6 +65,11 @@ raw_options = (
'url_prefix', None, 'url_prefix', None,
'Useful if you wish to run this server behind a reverse proxy.', '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', 'The interface on which to listen for connections',
'listen_on', '0.0.0.0', 'listen_on', '0.0.0.0',
'The default is to listen on all available interfaces. You can change this to, for' 'The default is to listen on all available interfaces. You can change this to, for'

View File

@ -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 <kovid at kovidgoyal.net>'
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()
# }}}

View File

@ -11,6 +11,7 @@ import sys, os
from calibre import as_unicode from calibre import as_unicode
from calibre.constants import plugins, iswindows from calibre.constants import plugins, iswindows
from calibre.srv.loop import ServerLoop from calibre.srv.loop import ServerLoop
from calibre.srv.plugins import BonJour
from calibre.srv.opts import opts_to_parser from calibre.srv.opts import opts_to_parser
from calibre.srv.http_response import create_http_handler from calibre.srv.http_response import create_http_handler
from calibre.srv.handler import Handler from calibre.srv.handler import Handler
@ -60,7 +61,10 @@ class Server(object):
if opts.log: if opts.log:
log = RotatingLog(opts.log, max_size=opts.max_log_size) log = RotatingLog(opts.log, max_size=opts.max_log_size)
self.handler = Handler(libraries, opts) 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.handler.set_log(self.loop.log)
self.serve_forever = self.loop.serve_forever self.serve_forever = self.loop.serve_forever

View File

@ -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: 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]) 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): def test_ring_buffer(self):
'Test the ring buffer used for reads' 'Test the ring buffer used for reads'
class FakeSocket(object): class FakeSocket(object):