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,
'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'

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.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

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:
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):