Using FS mtime to reload non recursive cache.
[pyTivo.git] / mind.py
blob9463de523b38df1b160c2179e27c3484bfb9e356
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 # It looks like tivo only supports one pc per house
44 pc_body_id = self.__pcBodySearch()[0]
46 data = {
47 'bodyId' : 'tsn:' + tsn,
48 'description' : description,
49 'duration' : duration,
50 'encodingType' : 'mpeg2ProgramStream',
51 'partnerId' : 'tivo:pt.3187',
52 'pcBodyId' : pc_body_id,
53 'publishDate' : time.strftime('%Y-%m-%d %H:%M%S', time.gmtime()),
54 'size' : size,
55 'source' : 'file:/C%3A%2FDocuments%20and%20Settings%2FStephanie%2FDesktop%2FVideo',
56 'state' : 'complete',
57 'subtitle' : subtitle,
58 'title' : title,
59 'url' : url,
62 offer_id, content_id = self.__bodyOfferModify(data)
63 self.__subscribe(offer_id, content_id, tsn)
65 def getDownloadRequests(self):
66 NEEDED_VALUES = [
67 'bodyId',
68 'bodyOfferId',
69 'description',
70 'partnerId',
71 'pcBodyId',
72 'publishDate',
73 'source',
74 'state',
75 'subscriptionId',
76 'subtitle',
77 'title',
78 'url',
81 # It looks like tivo only supports one pc per house
82 pc_body_id = self.__pcBodySearch()[0]
84 requests = []
85 offer_list = self.__bodyOfferSchedule(pc_body_id)
87 for offer in offer_list.findall('bodyOffer'):
88 d = {}
89 if offer.findtext('state') != 'scheduled':
90 continue
92 for n in NEEDED_VALUES:
93 d[n] = offer.findtext(n)
94 requests.append(d)
96 return requests
98 def completeDownloadRequest(self, request):
99 request['encodingType'] = 'mpeg2ProgramStream'
100 request['state'] = 'complete'
101 request['type'] = 'bodyOfferModify'
102 request['updateDate'] = time.strftime('%Y-%m-%d %H:%M%S', time.gmtime())
104 offer_id, content_id = self.__bodyOfferModify(request)
105 self.__subscribe(offer_id, content_id, request['bodyId'][4:])
108 def getXMPPLoginInfo(self):
109 # It looks like tivo only supports one pc per house
110 pc_body_id = self.__pcBodySearch()[0]
112 xml = self.__bodyXmppInfoGet(pc_body_id)
114 results = {}
115 results['server'] = xml.findtext('server')
116 results['port'] = int(xml.findtext('port'))
117 results['username'] = xml.findtext('xmppId')
119 for sendPresence in xml.findall('sendPresence'):
120 results.setdefault('presence_list', []).append(sendPresence.text)
122 return results
124 def __login(self):
126 data = {
127 'cams_security_domain' : 'tivocom',
128 'cams_login_config' : 'http',
129 'cams_cb_username' : self.__username,
130 'cams_cb_password' : self.__password,
131 'cams_original_url' : '/mind/mind7?type=infoGet'
134 r = urllib2.Request(
135 'https://mind.tivo.com:8181/mind/login',
136 urllib.urlencode(data)
138 try:
139 result = self.__opener.open(r)
140 except:
141 pass
143 self.__logger.debug('__login\n%s' % (data))
145 def __bodyOfferModify(self, data):
146 """Create an offer"""
147 r = urllib2.Request(
148 'https://mind.tivo.com:8181/mind/mind7?type=bodyOfferModify&bodyId=' + data['bodyId'],
149 dictcode(data),
150 {'Content-Type' : 'x-tivo/dict-binary'}
152 result = self.__opener.open(r)
154 xml = ElementTree.parse(result).find('.')
156 self.__logger.debug('__bodyOfferModify\n%s\n\n%sg' % (data, ElementTree.tostring(xml)))
158 if xml.findtext('state') != 'complete':
159 raise Exception(ElementTree.tostring(xml))
161 offer_id = xml.findtext('offerId')
162 content_id = offer_id.replace('of','ct')
164 return offer_id, content_id
167 def __subscribe(self, offer_id, content_id, tsn):
168 """Push the offer to the tivo"""
169 data = {
170 'bodyId' : 'tsn:' + tsn,
171 'idSetSource' : {
172 'contentId': content_id,
173 'offerId' : offer_id,
174 'type' : 'singleOfferSource',
176 'title' : 'pcBodySubscription',
177 'uiType' : 'cds',
180 r = urllib2.Request(
181 'https://mind.tivo.com:8181/mind/mind7?type=subscribe&bodyId=tsn:' + tsn,
182 dictcode(data),
183 {'Content-Type' : 'x-tivo/dict-binary'}
185 result = self.__opener.open(r)
187 xml = ElementTree.parse(result).find('.')
189 self.__logger.debug('__subscribe\n%s\n\n%sg' % (data, ElementTree.tostring(xml)))
191 return xml
193 def __bodyOfferSchedule(self, pc_body_id):
194 """Get pending stuff for this pc"""
196 data = {'pcBodyId' : pc_body_id,}
197 r = urllib2.Request(
198 'https://mind.tivo.com:8181/mind/mind7?type=bodyOfferSchedule',
199 dictcode(data),
200 {'Content-Type' : 'x-tivo/dict-binary'}
202 result = self.__opener.open(r)
204 xml = ElementTree.parse(result).find('.')
206 self.__logger.debug('bodyOfferSchedule\n%s\n\n%sg' % (data, ElementTree.tostring(xml)))
208 return xml
210 def __pcBodySearch(self):
211 """Find PCS"""
213 data = {}
214 r = urllib2.Request(
215 'https://mind.tivo.com:8181/mind/mind7?type=pcBodySearch',
216 dictcode(data),
217 {'Content-Type' : 'x-tivo/dict-binary'}
219 result = self.__opener.open(r)
221 xml = ElementTree.parse(result).find('.')
224 self.__logger.debug('__pcBodySearch\n%s\n\n%sg' % (data, ElementTree.tostring(xml)))
226 return [id.text for id in xml.findall('pcBody/pcBodyId')]
228 def __collectionIdSearch(self, url):
229 """Find collection ids"""
231 data = {'url' : url}
232 r = urllib2.Request(
233 'https://mind.tivo.com:8181/mind/mind7?type=collectionIdSearch',
234 dictcode(data),
235 {'Content-Type' : 'x-tivo/dict-binary'}
237 result = self.__opener.open(r)
239 xml = ElementTree.parse( result ).find('.')
240 collection_id = xml.findtext('collectionId')
242 self.__logger.debug('__collectionIdSearch\n%s\n\n%sg' % (data, ElementTree.tostring(xml)))
244 return collection_id
246 def __pcBodyStore(self, name, replace=False):
247 """Setup a new PC"""
249 data = {
250 'name' : name,
251 'replaceExisting' : str(replace).lower(),
254 r = urllib2.Request(
255 'https://mind.tivo.com:8181/mind/mind7?type=pcBodyStore',
256 dictcode(data),
257 {'Content-Type' : 'x-tivo/dict-binary'}
259 result = self.__opener.open(r)
261 xml = ElementTree.parse(result).find('.')
263 self.__logger.debug('__pcBodySearch\n%s\n\n%s' % (data, ElementTree.tostring(xml)))
265 return xml
267 def __bodyXmppInfoGet(self, body_id):
269 data = {
270 'bodyId' : body_id,
273 r = urllib2.Request(
274 'https://mind.tivo.com:8181/mind/mind7?type=bodyXmppInfoGet&bodyId=' + body_id,
275 dictcode(data),
276 {'Content-Type' : 'x-tivo/dict-binary'}
279 result = self.__opener.open(r)
281 xml = ElementTree.parse(result).find('.')
283 self.__logger.debug('__bodyXmppInfoGe\n%s\n\n%s' % (data, ElementTree.tostring(xml)))
285 return xml
288 def dictcode(d):
289 """Helper to create x-tivo/dict-binary"""
290 output = []
292 keys = [str(k) for k in d]
293 keys.sort()
295 for k in keys:
296 v = d[k]
298 output.append( varint( len(k) ) )
299 output.append( k )
301 if isinstance(v, dict):
302 output.append( struct.pack('>B', 0x02) )
303 output.append( dictcode(v) )
305 else:
306 v = unicode(v).encode('utf-8')
307 output.append( struct.pack('>B', 0x01) )
308 output.append( varint( len(v) ) )
309 output.append( v )
311 output.append( struct.pack('>B', 0x00) )
313 output.append( struct.pack('>B', 0x80) )
315 return ''.join(output)
317 def varint(i):
318 import sys
320 bits = []
321 while i:
322 bits.append(i & 0x01)
323 i = i >> 1
325 if not bits:
326 output = [0]
327 else:
328 output = []
330 while bits:
331 byte = 0
332 mybits = bits[:7]
333 del bits[:7]
335 for bit, p in zip(mybits, itertools.count()):
336 byte += bit * (2 ** p)
338 output.append(byte)
340 output[-1] = output[-1] | 0x80
341 return ''.join([chr(b) for b in output])
344 def getMind():
345 username = config.getTivoUsername()
346 password = config.getTivoPassword()
348 if not username or not password:
349 raise Exception("tivo_username and tivo_password required")
351 m = Mind(username, password)
353 return m