7 from threading
import Timer
8 from urllib
import quote
13 from plugin
import GetPlugin
15 SHARE_TEMPLATE
= '/TiVoConnect?Command=QueryContainer&Container=%s'
16 PLATFORM_MAIN
= 'pyTivo'
17 PLATFORM_VIDEO
= 'pc/pyTivo' # For the nice icon
20 def __init__(self
, names
):
23 def removeService(self
, server
, type, name
):
24 self
.names
.remove(name
.replace('.' + type, ''))
26 def addService(self
, server
, type, name
):
27 self
.names
.append(name
.replace('.' + type, ''))
30 def __init__(self
, logger
):
31 """ Announce our shares via Zeroconf. """
35 self
.rz
= zeroconf
.Zeroconf()
37 old_titles
= self
.scan()
38 address
= socket
.inet_aton(config
.get_ip())
39 port
= int(config
.getPort())
40 logger
.info('Announcing shares...')
41 for section
, settings
in config
.getShares():
43 ct
= GetPlugin(settings
['type']).CONTENT_TYPE
46 if ct
.startswith('x-container/'):
48 platform
= PLATFORM_VIDEO
50 platform
= PLATFORM_MAIN
51 logger
.info('Registering: %s' % section
)
52 self
.share_names
.append(section
)
53 desc
= {'path': SHARE_TEMPLATE
% quote(section
),
54 'platform': platform
, 'protocol': 'http',
55 'tsn': '{%s}' % uuid
.uuid4()}
59 while title
in old_titles
:
61 title
= '%s [%d]' % (section
, count
)
62 self
.renamed
[section
] = title
63 info
= zeroconf
.ServiceInfo('_%s._tcp.local.' % tt
,
64 '%s._%s._tcp.local.' % (title
, tt
),
65 address
, port
, 0, 0, desc
)
66 self
.rz
.registerService(info
)
67 self
.share_info
.append(info
)
70 """ Look for TiVos using Zeroconf. """
71 VIDS
= '_tivo-videos._tcp.local.'
74 self
.logger
.info('Scanning for TiVos...')
76 # Get the names of servers offering TiVo videos
77 browser
= zeroconf
.ServiceBrowser(self
.rz
, VIDS
, ZCListener(names
))
79 # Give them a second to respond
84 config
.tivos_found
= True
86 # Now get the addresses -- this is the slow part
88 info
= self
.rz
.getServiceInfo(VIDS
, name
+ '.' + VIDS
)
90 tsn
= info
.properties
.get('TSN')
91 if config
.get_server('togo_all'):
92 tsn
= info
.properties
.get('tsn', tsn
)
94 address
= socket
.inet_ntoa(info
.getAddress())
96 config
.tivos
[tsn
] = {'name': name
, 'address': address
,
98 config
.tivos
[tsn
].update(info
.properties
)
99 self
.logger
.info(name
)
104 self
.logger
.info('Unregistering: %s' % ' '.join(self
.share_names
))
105 for info
in self
.share_info
:
106 self
.rz
.unregisterService(info
)
111 self
.UDPSock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_DGRAM
)
112 self
.UDPSock
.setsockopt(socket
.SOL_SOCKET
, socket
.SO_BROADCAST
, 1)
115 self
.platform
= PLATFORM_VIDEO
116 for section
, settings
in config
.getShares():
118 ct
= GetPlugin(settings
['type']).CONTENT_TYPE
121 if ct
in ('x-container/tivo-music', 'x-container/tivo-photos'):
122 self
.platform
= PLATFORM_MAIN
126 logger
= logging
.getLogger('pyTivo.beacon')
128 self
.bd
= ZCBroadcast(logger
)
130 logger
.error('Zeroconf failure')
135 def add_service(self
, service
):
136 self
.services
.append(service
)
139 def format_services(self
):
140 return ';'.join(self
.services
)
142 def format_beacon(self
, conntype
, services
=True):
143 beacon
= ['tivoconnect=1',
144 'method=%s' % conntype
,
145 'identity={%s}' % config
.getGUID(),
146 'machine=%s' % socket
.gethostname(),
147 'platform=%s' % self
.platform
]
150 beacon
.append('services=' + self
.format_services())
152 beacon
.append('services=TiVoMediaServer:0/http')
154 return '\n'.join(beacon
) + '\n'
156 def send_beacon(self
):
157 beacon_ips
= config
.getBeaconAddresses()
158 beacon
= self
.format_beacon('broadcast')
159 for beacon_ip
in beacon_ips
.split():
160 if beacon_ip
!= 'listen':
164 result
= self
.UDPSock
.sendto(packet
, (beacon_ip
, 2190))
167 packet
= packet
[result
:]
173 self
.timer
= Timer(60, self
.start
)
181 def recv_bytes(self
, sock
, length
):
183 while len(block
) < length
:
184 add
= sock
.recv(length
- len(block
))
190 def recv_packet(self
, sock
):
191 length
= struct
.unpack('!I', self
.recv_bytes(sock
, 4))[0]
192 return self
.recv_bytes(sock
, length
)
194 def send_packet(self
, sock
, packet
):
195 sock
.sendall(struct
.pack('!I', len(packet
)) + packet
)
198 """ For the direct-connect, TCP-style beacon """
202 TCPSock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
203 TCPSock
.bind(('', 2190))
207 # Wait for a connection
208 client
, address
= TCPSock
.accept()
210 # Accept (and discard) the client's beacon
211 self
.recv_packet(client
)
214 self
.send_packet(client
, self
.format_beacon('connected'))
218 thread
.start_new_thread(server
, ())
220 def get_name(self
, address
):
221 """ Exchange beacons, and extract the machine name. """
222 our_beacon
= self
.format_beacon('connected', False)
223 machine_name
= re
.compile('machine=(.*)\n').search
226 tsock
= socket
.socket()
227 tsock
.connect((address
, 2190))
228 self
.send_packet(tsock
, our_beacon
)
229 tivo_beacon
= self
.recv_packet(tsock
)
231 name
= machine_name(tivo_beacon
).groups()[0]