Improvement for mobile web-GUI. Specifically clicking pyTiVo takes you back to the...
[pyTivo/wmcbrine/lucasnz.git] / beacon.py
blob2109d9f6fe0a781b5fdccb5f99eb72af41c08fa4
1 import logging
2 import re
3 import struct
4 import time
5 from socket import *
6 from threading import Timer
7 from urllib import quote
9 import Zeroconf
11 import config
12 from plugin import GetPlugin
14 SHARE_TEMPLATE = '/TiVoConnect?Command=QueryContainer&Container=%s'
16 class ZCListener:
17 def __init__(self, names):
18 self.names = names
20 def removeService(self, server, type, name):
21 if name in self.names:
22 self.names.remove(name)
24 def addService(self, server, type, name):
25 self.names.append(name)
27 class ZCBroadcast:
28 def __init__(self, logger):
29 """ Announce our shares via Zeroconf. """
30 self.share_names = []
31 self.share_info = []
32 self.logger = logger
33 self.rz = Zeroconf.Zeroconf()
34 address = inet_aton(config.get_ip())
35 port = int(config.getPort())
36 for section, settings in config.getShares():
37 ct = GetPlugin(settings['type']).CONTENT_TYPE
38 if ct.startswith('x-container/'):
39 logger.info('Registering: %s' % section)
40 self.share_names.append(section)
41 desc = {'path': SHARE_TEMPLATE % quote(section),
42 'platform': 'pc', 'protocol': 'http'}
43 tt = ct.split('/')[1]
44 info = Zeroconf.ServiceInfo('_%s._tcp.local.' % tt,
45 '%s._%s._tcp.local.' % (section, tt),
46 address, port, 0, 0, desc)
47 self.rz.registerService(info)
48 self.share_info.append(info)
50 def scan(self):
51 """ Look for TiVos using Zeroconf. """
52 VIDS = '_tivo-videos._tcp.local.'
53 names = []
55 # Get the names of servers offering TiVo videos
56 browser = Zeroconf.ServiceBrowser(self.rz, VIDS, ZCListener(names))
58 # Give them half a second to respond
59 time.sleep(0.5)
61 # Now get the addresses -- this is the slow part
62 for name in names:
63 info = self.rz.getServiceInfo(VIDS, name)
64 if info and 'TSN' in info.properties:
65 tsn = info.properties['TSN']
66 address = inet_ntoa(info.getAddress())
67 config.tivos[tsn] = address
68 name = name.replace('.' + VIDS, '')
69 self.logger.info(name)
70 config.tivo_names[tsn] = name
72 def shutdown(self):
73 self.logger.info('Unregistering: %s' % ' '.join(self.share_names))
74 for info in self.share_info:
75 self.rz.unregisterService(info)
76 self.rz.close()
78 class Beacon:
80 UDPSock = socket(AF_INET, SOCK_DGRAM)
81 UDPSock.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)
82 services = []
84 def __init__(self):
85 if config.get_zc():
86 logger = logging.getLogger('pyTivo.beacon')
87 try:
88 logger.info('Announcing shares...')
89 self.bd = ZCBroadcast(logger)
90 except:
91 logger.error('Zeroconf failure')
92 self.bd = None
93 else:
94 logger.info('Scanning for TiVos...')
95 self.bd.scan()
96 else:
97 self.bd = None
99 def add_service(self, service):
100 self.services.append(service)
101 self.send_beacon()
103 def format_services(self):
104 return ';'.join(self.services)
106 def format_beacon(self, conntype, services=True):
107 beacon = ['tivoconnect=1',
108 'swversion=1',
109 'method=%s' % conntype,
110 'identity=%s' % config.getGUID(),
111 'machine=%s' % gethostname(),
112 'platform=pc']
114 if services:
115 beacon.append('services=' + self.format_services())
116 else:
117 beacon.append('services=TiVoMediaServer:0/http')
119 return '\n'.join(beacon)
121 def send_beacon(self):
122 beacon_ips = config.getBeaconAddresses()
123 for beacon_ip in beacon_ips.split():
124 if beacon_ip != 'listen':
125 try:
126 self.UDPSock.sendto(self.format_beacon('broadcast'),
127 (beacon_ip, 2190))
128 except error, e:
129 print e
131 def start(self):
132 self.send_beacon()
133 self.timer = Timer(60, self.start)
134 self.timer.start()
136 def stop(self):
137 self.timer.cancel()
138 if self.bd:
139 self.bd.shutdown()
141 def listen(self):
142 """ For the direct-connect, TCP-style beacon """
143 import thread
145 def server():
146 TCPSock = socket(AF_INET, SOCK_STREAM)
147 TCPSock.bind(('', 2190))
148 TCPSock.listen(5)
150 while True:
151 # Wait for a connection
152 client, address = TCPSock.accept()
154 # Accept the client's beacon
155 client_length = struct.unpack('!I', client.recv(4))[0]
156 client_message = client.recv(client_length)
158 # Send ours
159 message = self.format_beacon('connected')
160 client.send(struct.pack('!I', len(message)))
161 client.send(message)
162 client.close()
164 thread.start_new_thread(server, ())
166 def get_name(self, address):
167 """ Exchange beacons, and extract the machine name. """
168 our_beacon = self.format_beacon('connected', False)
169 machine_name = re.compile('machine=(.*)\n').search
171 try:
172 tsock = socket()
173 tsock.connect((address, 2190))
175 tsock.send(struct.pack('!I', len(our_beacon)))
176 tsock.send(our_beacon)
178 length = struct.unpack('!I', tsock.recv(4))[0]
179 tivo_beacon = tsock.recv(length)
181 tsock.close()
183 name = machine_name(tivo_beacon).groups()[0]
184 except:
185 name = address
187 return name