Let Process on the xmpp connection block for a while
[pyTivo.git] / mind.py
blob761b96318952e017fef3a3aecc504c73a5b8d385
1 import cookielib
2 import urllib2
3 import urllib
4 import struct
5 import httplib
6 import time
7 import warnings
8 import itertools
9 import config
10 import logging
12 try:
13 import xml.etree.ElementTree as ElementTree
14 except ImportError:
15 try:
16 import elementtree.ElementTree as ElementTree
17 except ImportError:
18 warnings.warn('Python 2.5 or higher or elementtree is needed to use the TivoPush')
20 if 'ElementTree' not in locals():
22 class Mind:
23 def __init__(self, *arg, **karg):
24 raise Exception('Python 2.5 or higher or elementtree is needed to use the TivoPush')
26 else:
28 class Mind:
29 def __init__(self, username, password):
30 self.__logger = logging.getLogger('pyTivo.mind')
31 self.__username = username
32 self.__password = password
34 self.__cj = cookielib.CookieJar()
35 self.__opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.__cj))
37 self.__login()
39 if not self.__pcBodySearch():
40 self.__pcBodyStore('pyTivo', True)
42 def pushVideo(self, tsn, url, description, duration, size, title, subtitle):
43 data = {
44 'bodyId' : 'tsn:' + tsn,
45 'description' : description,
46 'duration' : duration,
47 'encodingType' : 'mpeg2ProgramStream',
48 'partnerId' : 'tivo:pt.3187',
49 'pcBodyId' : pc_body_id,
50 'publishDate' : time.strftime('%Y-%m-%d %H:%M%S', time.gmtime()),
51 'size' : size,
52 'source' : 'file:/C%3A%2FDocuments%20and%20Settings%2FStephanie%2FDesktop%2FVideo',
53 'state' : 'complete',
54 'subtitle' : subtitle,
55 'title' : title,
56 'url' : url,
59 # It looks like tivo only supports one pc per house
60 pc_body_id = self.__pcBodySearch()[0]
61 offer_id, content_id = self.__bodyOfferModify(data)
62 self.__subscribe(offer_id, content_id, tsn)
64 def getDownloadRequests(self):
65 NEEDED_VALUES = [
66 'bodyId',
67 'bodyOfferId',
68 'description',
69 'partnerId',
70 'pcBodyId',
71 'publishDate',
72 'source',
73 'state',
74 'subscriptionId',
75 'subtitle',
76 'title',
77 'url',
80 # It looks like tivo only supports one pc per house
81 pc_body_id = self.__pcBodySearch()[0]
83 requests = []
84 offer_list = self.__bodyOfferSchedule(pc_body_id)
86 for offer in offer_list.findall('bodyOffer'):
87 d = {}
88 if offer.findtext('state') != 'scheduled':
89 continue
91 for n in NEEDED_VALUES:
92 d[n] = offer.findtext(n)
93 requests.append(d)
95 return requests
97 def completeDownloadRequest(self, request):
98 request['encodingType'] = 'mpeg2ProgramStream'
99 request['state'] = 'complete'
100 request['type'] = 'bodyOfferModify'
101 request['updateDate'] = time.strftime('%Y-%m-%d %H:%M%S', time.gmtime())
103 offer_id, content_id = self.__bodyOfferModify(request)
104 self.__subscribe(offer_id, content_id, request['bodyId'][4:])
107 def getXMPPLoginInfo(self):
108 # It looks like tivo only supports one pc per house
109 pc_body_id = self.__pcBodySearch()[0]
111 xml = self.__bodyXmppInfoGet(pc_body_id)
113 results = {}
114 results['server'] = xml.findtext('server')
115 results['port'] = int(xml.findtext('port'))
116 results['username'] = xml.findtext('xmppId')
118 for sendPresence in xml.findall('sendPresence'):
119 results.setdefault('presence_list', []).append(sendPresence.text)
121 return results
123 def __login(self):
125 data = {
126 'cams_security_domain' : 'tivocom',
127 'cams_login_config' : 'http',
128 'cams_cb_username' : self.__username,
129 'cams_cb_password' : self.__password,
130 'cams_original_url' : '/mind/mind7?type=infoGet'
133 r = urllib2.Request(
134 'https://mind.tivo.com:8181/mind/login',
135 urllib.urlencode(data)
137 try:
138 result = self.__opener.open(r)
139 except:
140 pass
142 self.__logger.debug('__login\n%s' % (data))
144 def __bodyOfferModify(self, data):
145 """Create an offer"""
146 r = urllib2.Request(
147 'https://mind.tivo.com:8181/mind/mind7?type=bodyOfferModify&bodyId=' + data['bodyId'],
148 dictcode(data),
149 {'Content-Type' : 'x-tivo/dict-binary'}
151 result = self.__opener.open(r)
153 xml = ElementTree.parse(result).find('.')
155 self.__logger.debug('__bodyOfferModify\n%s\n\n%sg' % (data, ElementTree.tostring(xml)))
157 if xml.findtext('state') != 'complete':
158 raise Exception(ElementTree.tostring(xml))
160 offer_id = xml.findtext('offerId')
161 content_id = offer_id.replace('of','ct')
163 return offer_id, content_id
166 def __subscribe(self, offer_id, content_id, tsn):
167 """Push the offer to the tivo"""
168 data = {
169 'bodyId' : 'tsn:' + tsn,
170 'idSetSource' : {
171 'contentId': content_id,
172 'offerId' : offer_id,
173 'type' : 'singleOfferSource',
175 'title' : 'pcBodySubscription',
176 'uiType' : 'cds',
179 r = urllib2.Request(
180 'https://mind.tivo.com:8181/mind/mind7?type=subscribe&bodyId=tsn:' + tsn,
181 dictcode(data),
182 {'Content-Type' : 'x-tivo/dict-binary'}
184 result = self.__opener.open(r)
186 xml = ElementTree.parse(result).find('.')
188 self.__logger.debug('__subscribe\n%s\n\n%sg' % (data, ElementTree.tostring(xml)))
190 return xml
192 def __bodyOfferSchedule(self, pc_body_id):
193 """Get pending stuff for this pc"""
195 data = {'pcBodyId' : pc_body_id,}
196 r = urllib2.Request(
197 'https://mind.tivo.com:8181/mind/mind7?type=bodyOfferSchedule',
198 dictcode(data),
199 {'Content-Type' : 'x-tivo/dict-binary'}
201 result = self.__opener.open(r)
203 xml = ElementTree.parse(result).find('.')
205 self.__logger.debug('bodyOfferSchedule\n%s\n\n%sg' % (data, ElementTree.tostring(xml)))
207 return xml
209 def __pcBodySearch(self):
210 """Find PCS"""
212 data = {}
213 r = urllib2.Request(
214 'https://mind.tivo.com:8181/mind/mind7?type=pcBodySearch',
215 dictcode(data),
216 {'Content-Type' : 'x-tivo/dict-binary'}
218 result = self.__opener.open(r)
220 xml = ElementTree.parse(result).find('.')
223 self.__logger.debug('__pcBodySearch\n%s\n\n%sg' % (data, ElementTree.tostring(xml)))
225 return [id.text for id in xml.findall('pcBody/pcBodyId')]
227 def __collectionIdSearch(self, url):
228 """Find collection ids"""
230 data = {'url' : url}
231 r = urllib2.Request(
232 'https://mind.tivo.com:8181/mind/mind7?type=collectionIdSearch',
233 dictcode(data),
234 {'Content-Type' : 'x-tivo/dict-binary'}
236 result = self.__opener.open(r)
238 xml = ElementTree.parse( result ).find('.')
239 collection_id = xml.findtext('collectionId')
241 self.__logger.debug('__collectionIdSearch\n%s\n\n%sg' % (data, ElementTree.tostring(xml)))
243 return collection_id
245 def __pcBodyStore(self, name, replace=False):
246 """Setup a new PC"""
248 data = {
249 'name' : name,
250 'replaceExisting' : str(replace).lower(),
253 r = urllib2.Request(
254 'https://mind.tivo.com:8181/mind/mind7?type=pcBodyStore',
255 dictcode(data),
256 {'Content-Type' : 'x-tivo/dict-binary'}
258 result = self.__opener.open(r)
260 xml = ElementTree.parse(result).find('.')
262 self.__logger.debug('__pcBodySearch\n%s\n\n%s' % (data, ElementTree.tostring(xml)))
264 return xml
266 def __bodyXmppInfoGet(self, body_id):
268 data = {
269 'bodyId' : body_id,
272 r = urllib2.Request(
273 'https://mind.tivo.com:8181/mind/mind7?type=bodyXmppInfoGet&bodyId=' + body_id,
274 dictcode(data),
275 {'Content-Type' : 'x-tivo/dict-binary'}
278 result = self.__opener.open(r)
280 xml = ElementTree.parse(result).find('.')
282 self.__logger.debug('__bodyXmppInfoGe\n%s\n\n%s' % (data, ElementTree.tostring(xml)))
284 return xml
287 def dictcode(d):
288 """Helper to create x-tivo/dict-binary"""
289 output = []
291 keys = [str(k) for k in d]
292 keys.sort()
294 for k in keys:
295 v = d[k]
297 output.append( varint( len(k) ) )
298 output.append( k )
300 if isinstance(v, dict):
301 output.append( struct.pack('>B', 0x02) )
302 output.append( dictcode(v) )
304 else:
305 v = unicode(v).encode('utf-8')
306 output.append( struct.pack('>B', 0x01) )
307 output.append( varint( len(v) ) )
308 output.append( v )
310 output.append( struct.pack('>B', 0x00) )
312 output.append( struct.pack('>B', 0x80) )
314 return ''.join(output)
316 def varint(i):
317 import sys
319 bits = []
320 while i:
321 bits.append(i & 0x01)
322 i = i >> 1
324 if not bits:
325 output = [0]
326 else:
327 output = []
329 while bits:
330 byte = 0
331 mybits = bits[:7]
332 del bits[:7]
334 for bit, p in zip(mybits, itertools.count()):
335 byte += bit * (2 ** p)
337 output.append(byte)
339 output[-1] = output[-1] | 0x80
340 return ''.join([chr(b) for b in output])
343 def getMind():
344 username = config.getTivoUsername()
345 password = config.getTivoPassword()
347 if not username or not password:
348 raise Exception("tivo_username and tivo_password required")
350 m = Mind(username, password)
352 return m