discover/broadcast server location via mdns

This commit is contained in:
j 2017-06-30 19:26:20 +03:00
parent 4781c35643
commit fabbcb669e
5 changed files with 148 additions and 10 deletions

4
debian/control vendored
View file

@ -3,13 +3,13 @@ Maintainer: Jan Gerber <j@mailb.org>
Section: python
Priority: optional
Build-Depends: python3-all, debhelper (>= 7.4.3), dh-python, python3-setuptools (>= 0.6b3),
python3-ox, python3-six, python3-requests, python3-twisted
python3-ox, python3-six, python3-requests, python3-twisted, python3-zeroconf, python3-netifaces
Standards-Version: 3.9.1
Package: pandora-client
Architecture: all
Depends: python3, ${misc:Depends}, ${python3:Depends}, ffmpeg2theora, ffmpeg | libav-tools,
python3-ox, python3-six, python3-requests, python3-twisted
python3-ox, python3-six, python3-requests, python3-twisted, python3-zeroconf, python3-netifaces
Description: pandora_client is a commandline client for pan.do/ra.
You can use it to import videos into a pan.do/ra system.

View file

@ -987,15 +987,33 @@ class Client(object):
server.run(self, args)
def client(self, args):
if not args:
print('usage: %s client <server_url>\n\ti.e. %s client http://192.168.1.1:8789' % (sys.argv[0], sys.argv[0]))
sys.exit(1)
from . import client
url = args[0]
if len(args) == 1:
urls = [u for u in args if u.startswith('http:')]
name = [u for u in args if u not in urls]
if not name:
name = socket.gethostname()
else:
name = args[1]
name = name[0]
if not urls:
from . import localnode
nodes = localnode.LocalNodes()
time.sleep(1)
found = len(nodes)
if not found:
print('usage: %s client <server_url>\n\ti.e. %s client http://192.168.1.1:8789' % (sys.argv[0], sys.argv[0]))
sys.exit(1)
elif found > 1:
print('found multiple servers, please select one, your options are:')
for id, url in nodes.items():
print('\t%s client %s' % (sys.argv[0], url))
sys.exit(1)
else:
for id, url in nodes.items():
break
print('connecting to %s (%s)' % (id, url))
else:
url = urls[0]
from . import client
c = client.DistributedClient(url, name)
c.run()

107
pandora_client/localnode.py Normal file
View file

@ -0,0 +1,107 @@
import socket
import netifaces
from zeroconf import (
ServiceBrowser, ServiceInfo, ServiceStateChange, Zeroconf
)
import logging
logger = logging.getLogger(__name__)
service_type = '_pandoraclient._tcp.local.'
def get_broadcast_interfaces():
return list(set(
addr['addr']
for iface in netifaces.interfaces()
for addr in netifaces.ifaddresses(iface).get(socket.AF_INET, [])
if addr.get('netmask') != '255.255.255.255' and addr.get('broadcast')
))
class Server(object):
local_info = None
local_ips = None
def __init__(self, port):
self.port = port
self.name = socket.gethostname().partition('.')[0] + '-%s' % port
self.local_ips = get_broadcast_interfaces()
self.zeroconf = {ip: Zeroconf(interfaces=[ip]) for ip in self.local_ips}
self.register_service()
def register_service(self):
if self.local_info:
for local_ip, local_info in self.local_info:
self.zeroconf[local_ip].unregister_service(local_info)
self.local_info = None
local_name = socket.gethostname().partition('.')[0] + '.local.'
port = self.port
desc = {}
self.local_info = []
for i, local_ip in enumerate(get_broadcast_interfaces()):
if i:
name = '%s-%s.%s' % (self.name, i+1, service_type)
else:
name = '%s.%s' % (self.name, service_type)
local_info = ServiceInfo(service_type, name,
socket.inet_aton(local_ip), port, 0, 0, desc, local_name)
self.zeroconf[local_ip].register_service(local_info)
self.local_info.append((local_ip, local_info))
def __del__(self):
self.close()
def close(self):
if self.local_info:
for local_ip, local_info in self.local_info:
try:
self.zeroconf[local_ip].unregister_service(local_info)
except:
logger.debug('exception closing zeroconf', exc_info=True)
self.local_info = None
if self.zeroconf:
for local_ip in self.zeroconf:
try:
self.zeroconf[local_ip].close()
except:
logger.debug('exception closing zeroconf', exc_info=True)
self.zeroconf = None
class LocalNodes(dict):
def __init__(self):
self.local_ips = get_broadcast_interfaces()
self.zeroconf = {ip: Zeroconf(interfaces=[ip]) for ip in self.local_ips}
self.browse()
def browse(self):
self.browser = {
ip: ServiceBrowser(self.zeroconf[ip], service_type, handlers=[self.on_service_state_change])
for ip in self.zeroconf
}
def __del__(self):
self.close()
def close(self):
if self.zeroconf:
for local_ip in self.zeroconf:
try:
self.zeroconf[local_ip].close()
except:
logger.debug('exception closing zeroconf', exc_info=True)
self.zeroconf = None
for id in list(self):
self.pop(id, None)
def on_service_state_change(self, zeroconf, service_type, name, state_change):
id = name.split('.')[0].split('-')[0]
if state_change is ServiceStateChange.Added:
info = zeroconf.get_service_info(service_type, name)
if info:
self[id] = 'http://%s:%s' % (socket.inet_ntoa(info.address), info.port)
elif state_change is ServiceStateChange.Removed:
logger.debug('remove: %s', id)
self.pop(id, None)

View file

@ -205,7 +205,18 @@ def run(client, args=None):
port = int(port)
else:
port = int(args[0])
local = None
else:
try:
from . import localnode
local = localnode.Server(port)
except:
local = None
reactor.listenTCP(port, site, interface=interface)
print('listening on http://%s:%s' % (interface, port))
if local:
print('broadcasting location via mdns (you can just use pandora_client client on the other side)')
client.update_encodes()
reactor.run()
if local:
del local

View file

@ -52,7 +52,9 @@ setup(
install_requires=[
'ox >= 2.1.541,<3',
'six',
'requests >= 1.1.0'
'requests >= 1.1.0',
'zeroconf',
'netifaces',
],
keywords=[],
classifiers=[