Navigation links for Push screens.
[pyTivo/wmcbrine/lucasnz.git] / beacon.py
blob3248d65dd6661208045e1c749af1a6059e57a91f
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'
15 PLATFORM_MAIN = 'pyTivo'
16 PLATFORM_VIDEO = 'pc/pyTivo' # For the nice icon
18 class ZCListener:
19 def __init__(self, names):
20 self.names = names
22 def removeService(self, server, type, name):
23 self.names.remove(name.replace('.' + type, ''))
25 def addService(self, server, type, name):
26 self.names.append(name.replace('.' + type, ''))
28 class ZCBroadcast:
29 def __init__(self, logger):
30 """ Announce our shares via Zeroconf. """
31 self.share_names = []
32 self.share_info = []
33 self.logger = logger
34 self.rz = Zeroconf.Zeroconf()
35 self.renamed = {}
36 old_titles = self.scan()
37 address = inet_aton(config.get_ip())
38 port = int(config.getPort())
39 logger.info('Announcing shares...')
40 for section, settings in config.getShares():
41 ct = GetPlugin(settings['type']).CONTENT_TYPE
42 if ct.startswith('x-container/'):
43 if 'video' in ct:
44 platform = PLATFORM_VIDEO
45 else:
46 platform = PLATFORM_MAIN
47 logger.info('Registering: %s' % section)
48 self.share_names.append(section)
49 desc = {'path': SHARE_TEMPLATE % quote(section),
50 'platform': platform, 'protocol': 'http'}
51 tt = ct.split('/')[1]
52 title = section
53 count = 1
54 while title in old_titles:
55 count += 1
56 title = '%s [%d]' % (section, count)
57 self.renamed[section] = title
58 info = Zeroconf.ServiceInfo('_%s._tcp.local.' % tt,
59 '%s._%s._tcp.local.' % (title, tt),
60 address, port, 0, 0, desc)
61 self.rz.registerService(info)
62 self.share_info.append(info)
64 def scan(self):
65 """ Look for TiVos using Zeroconf. """
66 VIDS = '_tivo-videos._tcp.local.'
67 names = []
69 self.logger.info('Scanning for TiVos...')
71 # Get the names of servers offering TiVo videos
72 browser = Zeroconf.ServiceBrowser(self.rz, VIDS, ZCListener(names))
74 # Give them half a second to respond
75 time.sleep(0.5)
77 # Now get the addresses -- this is the slow part
78 for name in names:
79 info = self.rz.getServiceInfo(VIDS, name + '.' + VIDS)
80 if info and 'TSN' in info.properties:
81 tsn = info.properties['TSN']
82 address = inet_ntoa(info.getAddress())
83 config.tivos[tsn] = address
84 self.logger.info(name)
85 config.tivo_names[tsn] = name
87 return names
89 def shutdown(self):
90 self.logger.info('Unregistering: %s' % ' '.join(self.share_names))
91 for info in self.share_info:
92 self.rz.unregisterService(info)
93 self.rz.close()
95 class Beacon:
96 def __init__(self):
97 self.UDPSock = socket(AF_INET, SOCK_DGRAM)
98 self.UDPSock.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)
99 self.services = []
101 self.platform = PLATFORM_VIDEO
102 for section, settings in config.getShares():
103 ct = GetPlugin(settings['type']).CONTENT_TYPE
104 if ct in ('x-container/tivo-music', 'x-container/tivo-photos'):
105 self.platform = PLATFORM_MAIN
106 break
108 if config.get_zc():
109 logger = logging.getLogger('pyTivo.beacon')
110 try:
111 self.bd = ZCBroadcast(logger)
112 except:
113 logger.error('Zeroconf failure')
114 self.bd = None
115 else:
116 self.bd = None
118 def add_service(self, service):
119 self.services.append(service)
120 self.send_beacon()
122 def format_services(self):
123 return ';'.join(self.services)
125 def format_beacon(self, conntype, services=True):
126 beacon = ['tivoconnect=1',
127 'method=%s' % conntype,
128 'identity=%s' % config.getGUID(),
129 'machine=%s' % gethostname(),
130 'platform=%s' % self.platform]
132 if services:
133 beacon.append('services=' + self.format_services())
134 else:
135 beacon.append('services=TiVoMediaServer:0/http')
137 return '\n'.join(beacon)
139 def send_beacon(self):
140 beacon_ips = config.getBeaconAddresses()
141 for beacon_ip in beacon_ips.split():
142 if beacon_ip != 'listen':
143 try:
144 self.UDPSock.sendto(self.format_beacon('broadcast'),
145 (beacon_ip, 2190))
146 except error, e:
147 print e
149 def start(self):
150 self.send_beacon()
151 self.timer = Timer(60, self.start)
152 self.timer.start()
154 def stop(self):
155 self.timer.cancel()
156 if self.bd:
157 self.bd.shutdown()
159 def listen(self):
160 """ For the direct-connect, TCP-style beacon """
161 import thread
163 def server():
164 TCPSock = socket(AF_INET, SOCK_STREAM)
165 TCPSock.bind(('', 2190))
166 TCPSock.listen(5)
168 while True:
169 # Wait for a connection
170 client, address = TCPSock.accept()
172 # Accept the client's beacon
173 client_length = struct.unpack('!I', client.recv(4))[0]
174 client_message = client.recv(client_length)
176 # Send ours
177 message = self.format_beacon('connected')
178 client.send(struct.pack('!I', len(message)))
179 client.send(message)
180 client.close()
182 thread.start_new_thread(server, ())
184 def get_name(self, address):
185 """ Exchange beacons, and extract the machine name. """
186 our_beacon = self.format_beacon('connected', False)
187 machine_name = re.compile('machine=(.*)\n').search
189 try:
190 tsock = socket()
191 tsock.connect((address, 2190))
193 tsock.send(struct.pack('!I', len(our_beacon)))
194 tsock.send(our_beacon)
196 length = struct.unpack('!I', tsock.recv(4))[0]
197 tivo_beacon = tsock.recv(length)
199 tsock.close()
201 name = machine_name(tivo_beacon).groups()[0]
202 except:
203 name = address
205 return name