Don't show CopyProtected shows in NPL
[pyTivo/krkeegan.git] / plugins / admin / admin.py
blob1a920b2e5ab76fda4aa7af0e013945d37883b57f
1 import os, socket, re, sys, ConfigParser, config, time
2 import urllib2, cookielib, thread, buildhelp
3 from xml.dom import minidom
4 from ConfigParser import NoOptionError
5 from Cheetah.Template import Template
6 from plugin import Plugin
7 from urllib import unquote_plus, quote, unquote
8 from urlparse import urlparse
9 from xml.sax.saxutils import escape
10 from lrucache import LRUCache
11 import debug
13 SCRIPTDIR = os.path.dirname(__file__)
15 CLASS_NAME = 'Admin'
17 p = os.path.dirname(__file__)
18 p = p.split(os.path.sep)
19 p.pop()
20 p.pop()
21 p = os.path.sep.join(p)
22 config_file_path = os.path.join(p, 'pyTivo.conf')
24 status = {} #Global variable to control download threads
25 tivo_cache = {} #Cache of TiVo NPL
27 class Admin(Plugin):
28 CONTENT_TYPE = 'text/html'
30 def Reset(self, handler, query):
31 config.reset()
32 handler.server.reset()
33 if 'last_page' in query:
34 last_page = query['last_page'][0]
35 else:
36 last_page = 'Admin'
38 subcname = query['Container'][0]
39 cname = subcname.split('/')[0]
40 handler.send_response(200)
41 handler.end_headers()
42 t = Template(file=os.path.join(SCRIPTDIR,'templates', 'redirect.tmpl'))
43 t.container = cname
44 t.time = '3'
45 t.url = '/TiVoConnect?Command='+ last_page +'&Container=' + quote(cname)
46 t.text = '<h3>The pyTivo Server has been soft reset.</h3> <br>pyTivo has reloaded the pyTivo.conf'+\
47 'file and all changed should now be in effect. <br> The'+ \
48 '<a href="/TiVoConnect?Command='+ quote(last_page) +'&Container='+ quote(cname) +'"> previous</a> page will reload in 3 seconds.'
49 handler.wfile.write(t)
50 debug.debug_write(__name__, debug.fn_attr(), ['The pyTivo Server has been soft reset.'])
51 debug.print_conf(__name__, debug.fn_attr())
53 def Admin(self, handler, query):
54 #Read config file new each time in case there was any outside edits
55 config = ConfigParser.ConfigParser()
56 config.read(config_file_path)
58 shares_data = []
59 for section in config.sections():
60 if not(section.startswith('_tivo_') or section.startswith('Server')):
61 if not(config.has_option(section,'type')):
62 shares_data.append((section, dict(config.items(section, raw=True))))
63 elif config.get(section,'type').lower() != 'admin':
64 shares_data.append((section, dict(config.items(section, raw=True))))
66 subcname = query['Container'][0]
67 cname = subcname.split('/')[0]
68 handler.send_response(200)
69 handler.end_headers()
70 t = Template(file=os.path.join(SCRIPTDIR,'templates', 'settings.tmpl'))
71 t.container = cname
72 t.quote = quote
73 t.server_data = dict(config.items('Server', raw=True))
74 t.server_known = buildhelp.getknown('server')
75 t.shares_data = shares_data
76 t.shares_known = buildhelp.getknown('shares')
77 t.tivos_data = [ (section, dict(config.items(section, raw=True))) for section in config.sections() \
78 if section.startswith('_tivo_')]
79 t.tivos_known = buildhelp.getknown('tivos')
80 t.help_list = buildhelp.gethelp()
81 handler.wfile.write(t)
83 def UpdateSettings(self, handler, query):
84 config = ConfigParser.ConfigParser()
85 config.read(config_file_path)
86 for key in query:
87 if key.startswith('Server.'):
88 section, option = key.split('.')
89 if option == "new__setting":
90 new_setting = query[key][0]
91 continue
92 if option == "new__value":
93 new_value = query[key][0]
94 continue
95 if query[key][0] == " ":
96 config.remove_option(section, option)
97 else:
98 config.set(section, option, query[key][0])
99 if not(new_setting == ' ' and new_value == ' '):
100 config.set('Server', new_setting, new_value)
102 sections = query['Section_Map'][0].split(']')
103 sections.pop() #last item is junk
104 for section in sections:
105 ID, name = section.split('|')
106 if query[ID][0] == "Delete_Me":
107 config.remove_section(name)
108 continue
109 if query[ID][0] != name:
110 config.remove_section(name)
111 config.add_section(query[ID][0])
112 for key in query:
113 if key.startswith(ID + '.'):
114 junk, option = key.split('.')
115 if option == "new__setting":
116 new_setting = query[key][0]
117 continue
118 if option == "new__value":
119 new_value = query[key][0]
120 continue
121 if query[key][0] == " ":
122 config.remove_option(query[ID][0], option)
123 else:
124 config.set(query[ID][0], option, query[key][0])
125 if not(new_setting == ' ' and new_value == ' '):
126 config.set(query[ID][0], new_setting, new_value)
127 if query['new_Section'][0] != " ":
128 config.add_section(query['new_Section'][0])
129 f = open(config_file_path, "w")
130 config.write(f)
131 f.close()
133 subcname = query['Container'][0]
134 cname = subcname.split('/')[0]
135 handler.send_response(200)
136 handler.end_headers()
137 t = Template(file=os.path.join(SCRIPTDIR,'templates', 'redirect.tmpl'))
138 t.container = cname
139 t.time = '10'
140 t.url = '/TiVoConnect?Command=Admin&Container=' + quote(cname)
141 t.text = '<h3>Your Settings have been saved.</h3> <br>You settings have been saved to the pyTivo.conf file.'+\
142 'However you will need to do a <b>Soft Reset</b> before these changes will take effect.'+\
143 '<br> The <a href="/TiVoConnect?Command=Admin&Container='+ quote(cname) +'"> Admin</a> page will reload in 10 seconds.'
144 handler.wfile.write(t)
146 def NPL(self, handler, query):
147 shows_per_page = 50 #Change this to alter the number of shows returned per page
148 subcname = query['Container'][0]
149 cname = subcname.split('/')[0]
150 folder = ''
151 AnchorItem = ''
152 AnchorOffset= ''
153 for name, data in config.getShares():
154 if cname == name:
155 if 'tivo_mak' in data:
156 tivo_mak = data['tivo_mak']
157 else:
158 tivo_mak = ""
159 if 'togo_path' in data:
160 togo_path = data['togo_path']
161 else:
162 togo_path = ""
164 if 'TiVo' in query:
165 tivoIP = query['TiVo'][0]
166 theurl = 'https://'+ tivoIP +'/TiVoConnect?Command=QueryContainer&ItemCount='+ str(shows_per_page) +'&Container=/NowPlaying'
167 if 'Folder' in query:
168 folder += str(query['Folder'][0])
169 theurl += '/' + folder
170 if 'AnchorItem' in query:
171 AnchorItem += str(query['AnchorItem'][0])
172 theurl += '&AnchorItem=' + quote(AnchorItem)
173 if 'AnchorOffset' in query:
174 AnchorOffset += str(query['AnchorOffset'][0])
175 theurl += '&AnchorOffset=' + AnchorOffset
177 password = tivo_mak #TiVo MAK
179 r=urllib2.Request(theurl)
180 auth_handler = urllib2.HTTPDigestAuthHandler()
181 auth_handler.add_password('TiVo DVR', tivoIP, 'tivo', password)
182 opener = urllib2.build_opener(auth_handler)
183 urllib2.install_opener(opener)
185 if theurl in tivo_cache: #check to see if we have accessed this page before
186 if tivo_cache[theurl]['thepage'] == '' or (time.time() - tivo_cache[theurl]['thepage_time']) >= 60: #if page is empty or old then retreive it
187 try:
188 handle = urllib2.urlopen(r)
189 except IOError, e:
190 handler.send_response(200)
191 handler.end_headers()
192 t = Template(file=os.path.join(SCRIPTDIR,'templates', 'redirect.tmpl'))
193 t.container = cname
194 t.time = '20'
195 t.url = '/TiVoConnect?Command=NPL&Container=' + quote(cname)
196 t.text = '<h3>Unable to Connect to TiVo.</h3> <br>pyTivo was unable to connect to the TiVo at ' + tivoIP +\
197 '<br>This most likely caused by an incorrect Media Access Key. Please return to the ToGo page and double check your Media Access Key.' +\
198 '<br> The <a href="/TiVoConnect?Command=NPL&Container='+ quote(cname) + '"> ToGo</a> page will reload in 20 seconds.'
199 handler.wfile.write(t)
200 return
201 tivo_cache[theurl]['thepage'] = handle.read()
202 tivo_cache[theurl]['thepage_time'] = time.time()
203 else: #not in cache
204 try:
205 handle = urllib2.urlopen(r)
206 except IOError, e:
207 handler.send_response(200)
208 handler.end_headers()
209 t = Template(file=os.path.join(SCRIPTDIR,'templates', 'redirect.tmpl'))
210 t.container = cname
211 t.time = '20'
212 t.url = '/TiVoConnect?Command=NPL&Container=' + quote(cname)
213 t.text = '<h3>Unable to Connect to TiVo.</h3> <br>pyTivo was unable to connect to the TiVo at ' + tivoIP +\
214 '<br>This most likely caused by an incorrect Media Access Key. Please return to the ToGo page and double check your Media Access Key.' +\
215 '<br> The <a href="/TiVoConnect?Command=NPL&Container='+ quote(cname) + '"> ToGo</a> page will reload in 20 seconds.'
216 handler.wfile.write(t)
217 return
218 tivo_cache[theurl] = {}
219 tivo_cache[theurl]['thepage'] = handle.read()
220 tivo_cache[theurl]['thepage_time'] = time.time()
222 xmldoc = minidom.parseString(tivo_cache[theurl]['thepage'])
223 items = xmldoc.getElementsByTagName('Item')
224 TotalItems = xmldoc.getElementsByTagName('Details')[0].getElementsByTagName('TotalItems')[0].firstChild.data
225 ItemStart = xmldoc.getElementsByTagName('ItemStart')[0].firstChild.data
226 ItemCount = xmldoc.getElementsByTagName('ItemCount')[0].firstChild.data
227 FirstAnchor = items[0].getElementsByTagName("Links")[0].getElementsByTagName("Content")[0].getElementsByTagName("Url")[0].firstChild.data
229 data = []
230 for item in items:
231 entry = {}
232 entry['Title'] = item.getElementsByTagName("Title")[0].firstChild.data
233 entry['ContentType'] = item.getElementsByTagName("ContentType")[0].firstChild.data
234 if (len(item.getElementsByTagName("CopyProtected")) >= 1):
235 entry['CopyProtected'] = item.getElementsByTagName("CopyProtected")[0].firstChild.data
236 if (len(item.getElementsByTagName("UniqueId")) >= 1):
237 entry['UniqueId'] = item.getElementsByTagName("UniqueId")[0].firstChild.data
238 if entry['ContentType'] == 'x-tivo-container/folder':
239 entry['TotalItems'] = item.getElementsByTagName("TotalItems")[0].firstChild.data
240 entry['LastChangeDate'] = item.getElementsByTagName("LastChangeDate")[0].firstChild.data
241 entry['LastChangeDate'] = time.strftime("%b %d, %Y", time.localtime(int(entry['LastChangeDate'], 16)))
242 else:
243 link = item.getElementsByTagName("Links")[0]
244 if (len(link.getElementsByTagName("CustomIcon")) >= 1):
245 entry['Icon'] = link.getElementsByTagName("CustomIcon")[0].getElementsByTagName("Url")[0].firstChild.data
246 if (len(link.getElementsByTagName("Content")) >= 1):
247 entry['Url'] = link.getElementsByTagName("Content")[0].getElementsByTagName("Url")[0].firstChild.data
248 parse_url = urlparse(entry['Url'])
249 entry['Url'] = quote('http://' + parse_url[1].split(':')[0] + parse_url[2] + "?" + parse_url[4])
250 keys = ['SourceSize', 'Duration', 'CaptureDate', 'EpisodeTitle', 'Description', 'SourceChannel', 'SourceStation']
251 for key in keys:
252 try:
253 entry[key] = item.getElementsByTagName(key)[0].firstChild.data
254 except:
255 entry[key] = ''
256 entry['SourceSize'] = "%.3f GB" % float(float(entry['SourceSize'])/(1024*1024*1024))
257 entry['Duration'] = str(int(entry['Duration'])/(60*60*1000)).zfill(2) + ':' \
258 + str((int(entry['Duration'])%(60*60*1000))/(60*1000)).zfill(2) + ':' \
259 + str((int(entry['Duration'])/1000)%60).zfill(2)
260 entry['CaptureDate'] = time.strftime("%b %d, %Y", time.localtime(int(entry['CaptureDate'], 16)))
262 data.append(entry)
263 else:
264 data = []
265 tivoIP = ''
266 TotalItems = 0
267 ItemStart = 0
268 ItemCount = 0
269 FirstAnchor = ''
271 subcname = query['Container'][0]
272 cname = subcname.split('/')[0]
273 handler.send_response(200)
274 handler.end_headers()
275 t = Template(file=os.path.join(SCRIPTDIR,'templates', 'npl.tmpl'))
276 t.quote = quote
277 t.folder = folder
278 t.status = status
279 t.tivo_mak = tivo_mak
280 t.togo_path = togo_path
281 t.tivos = handler.tivos
282 t.tivoIP = tivoIP
283 t.container = cname
284 t.data = data
285 t.unquote = unquote
286 t.len = len
287 t.TotalItems = int(TotalItems)
288 t.ItemStart = int(ItemStart)
289 t.ItemCount = int(ItemCount)
290 t.FirstAnchor = quote(FirstAnchor)
291 t.shows_per_page = shows_per_page
292 o = ''.join([i for i in unicode(t) if i not in (u'\u200b')])
293 handler.wfile.write(o.encode('latin-1'))
295 def get_tivo_file(self, url, mak, tivoIP, outfile):
296 #global status
297 cj = cookielib.LWPCookieJar()
299 r=urllib2.Request(url)
300 auth_handler = urllib2.HTTPDigestAuthHandler()
301 auth_handler.add_password('TiVo DVR', tivoIP, 'tivo', mak)
302 opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj), auth_handler)
303 urllib2.install_opener(opener)
305 try:
306 handle = urllib2.urlopen(r)
307 except IOError, e:
308 #If we get "Too many transfers error" try a second time. For some reason
309 #urllib2 does not properly close connections when a transfer is canceled.
310 if e.code == 503:
311 try:
312 handle = urllib2.urlopen(r)
313 except IOError, e:
314 status[url]['running'] = False
315 status[url]['error'] = e.code
316 return
317 else:
318 status[url]['running'] = False
319 status[url]['error'] = e.code
320 return
322 f = open(outfile, 'wb')
323 kilobytes = 0
324 start_time = time.time()
325 output = handle.read(1024)
326 while status[url]['running'] and output != '':
327 kilobytes += 1
328 f.write(output)
329 if ((time.time() - start_time) >= 5):
330 status[url]['rate'] = int(kilobytes/(time.time() - start_time))
331 kilobytes = 0
332 start_time = time.time()
333 output = handle.read(1024)
334 status[url]['running'] = False
335 handle.close()
336 f.close()
337 return
339 def ToGo(self, handler, query):
340 subcname = query['Container'][0]
341 cname = subcname.split('/')[0]
342 tivoIP = query['TiVo'][0]
343 for name, data in config.getShares():
344 if cname == name:
345 if 'tivo_mak' in data:
346 tivo_mak = data['tivo_mak']
347 else:
348 tivo_mak = ""
349 if 'togo_path' in data:
350 togo_path = data['togo_path']
351 else:
352 togo_path = ""
353 if tivo_mak != "" and togo_path != "":
354 parse_url = urlparse(str(query['Url'][0]))
355 theurl = 'http://' + parse_url[1].split(':')[0] + parse_url[2] + "?" + parse_url[4]
356 password = tivo_mak #TiVo MAK
357 name = unquote(parse_url[2])[10:300].split('.')
358 name.insert(-1," - " + unquote(parse_url[4]).split("id=")[1] + ".")
359 outfile = os.path.join(togo_path, "".join(name))
361 status[theurl] = {'running':True, 'error':'', 'rate':'', 'finished':False}
363 thread.start_new_thread(Admin.get_tivo_file, (self, theurl, password, tivoIP, outfile))
365 handler.send_response(200)
366 handler.end_headers()
367 t = Template(file=os.path.join(SCRIPTDIR,'templates', 'redirect.tmpl'))
368 command = query['Redirect'][0]
369 t.time = '3'
370 t.url = '/TiVoConnect?Command='+ command +'&Container='+ quote(cname) +'&TiVo=' + tivoIP
371 t.text = '<h3>Transfer Initiated.</h3> <br>You selected transfer has been initiated.'+\
372 '<br> The <a href="/TiVoConnect?Command='+ command +'&Container='+ quote(cname) +'&TiVo=' + tivoIP +'"> ToGo</a> page will reload in 3 seconds.'
373 handler.wfile.write(t)
374 else:
375 handler.send_response(200)
376 handler.end_headers()
377 t = Template(file=os.path.join(SCRIPTDIR,'templates', 'redirect.tmpl'))
378 command = query['Redirect'][0]
379 t.time = '10'
380 t.url = '/TiVoConnect?Command='+ command +'&Container='+ quote(cname) +'&TiVo=' + tivoIP
381 t.text = '<h3>Missing Data.</h3> <br>You must set both "tivo_mak" and "togo_path" before using this function.'+\
382 '<br> The <a href="/TiVoConnect?Command='+ command +'&Container='+ quote(cname) +'&TiVo=' + tivoIP +'"> ToGo</a> page will reload in 10 seconds.'
383 handler.wfile.write(t)
385 def ToGoStop(self, handler, query):
386 parse_url = urlparse(str(query['Url'][0]))
387 theurl = 'http://' + parse_url[1].split(':')[0] + parse_url[2] + "?" + parse_url[4]
389 status[theurl]['running'] = False
391 subcname = query['Container'][0]
392 cname = subcname.split('/')[0]
393 tivoIP = query['TiVo'][0]
394 command = query['Redirect'][0]
395 handler.send_response(200)
396 handler.end_headers()
397 t = Template(file=os.path.join(SCRIPTDIR,'templates', 'redirect.tmpl'))
398 t.time = '3'
399 t.url = '/TiVoConnect?Command='+ command +'&Container='+ quote(cname) +'&TiVo=' + tivoIP
400 t.text = '<h3>Transfer Stopped.</h3> <br>Your transfer has been stopped.'+\
401 '<br> The <a href="/TiVoConnect?Command='+ command +'&Container='+ quote(cname) +'&TiVo=' + tivoIP +'"> ToGo</a> page will reload in 3 seconds.'
402 handler.wfile.write(t)
405 def SaveNPL(self, handler, query):
406 config = ConfigParser.ConfigParser()
407 config.read(config_file_path)
408 if 'tivo_mak' in query:
409 config.set(query['Container'][0], 'tivo_mak', query['tivo_mak'][0])
410 if 'togo_path' in query:
411 config.set(query['Container'][0], 'togo_path', query['togo_path'][0])
412 f = open(config_file_path, "w")
413 config.write(f)
414 f.close()
416 subcname = query['Container'][0]
417 cname = subcname.split('/')[0]
418 handler.send_response(200)
419 handler.end_headers()
420 t = Template(file=os.path.join(SCRIPTDIR,'templates', 'redirect.tmpl'))
421 t.container = cname
422 t.time = '2'
423 t.url = '/TiVoConnect?last_page=NPL&Command=Reset&Container=' + quote(cname)
424 t.text = '<h3>Your Settings have been saved.</h3> <br>You settings have been saved to the pyTivo.conf file.'+\
425 'pyTivo will now do a <b>Soft Reset</b> to allow these changes to take effect.'+\
426 '<br> The <a href="/TiVoConnect?last_page=NPL&Command=Reset&Container='+ quote(cname) +'"> Reset</a> will occur in 2 seconds.'
427 handler.wfile.write(t)