Re-ordered the import lines to fit with PEP 8.
[pyTivo/wgw.git] / plugins / admin / admin.py
blob020a21d995f337bfe11d4a039c5608b1d2072563
1 import ConfigParser
2 import cookielib
3 import os
4 import socket
5 import re
6 import sys
7 import thread
8 import time
9 import urllib2
10 from ConfigParser import NoOptionError
11 from urllib import unquote_plus, quote, unquote
12 from urlparse import urlparse
13 from xml.dom import minidom
14 from xml.sax.saxutils import escape
16 from lrucache import LRUCache
17 from Cheetah.Template import Template
18 import buildhelp
19 import config
20 import logging
21 from plugin import Plugin
23 SCRIPTDIR = os.path.dirname(__file__)
25 CLASS_NAME = 'Admin'
27 # Some error/status message templates
29 MISSING = """<h3>Missing Data.</h3> <br>
30 You must set both "tivo_mak" and "togo_path" before using this
31 function.<br>
32 The <a href="/TiVoConnect?Command=%s&Container=%s&TiVo=%s">ToGo</a> page
33 will reload in 10 seconds."""
35 RESET_MSG = """<h3>The pyTivo Server has been soft reset.</h3> <br>
36 pyTivo has reloaded the pyTivo.conf file and all changes should now be
37 in effect. <br>
38 The <a href="/TiVoConnect?Command=%s&Container=%s">previous</a> page
39 will reload in 3 seconds."""
41 SETTINGS1 = """<h3>Your Settings have been saved.</h3> <br>
42 Your settings have been saved to the pyTivo.conf file. However you will
43 need to do a <b>Soft Reset</b> before these changes will take effect.<br>
44 The <a href="/TiVoConnect?Command=Admin&Container=%s">Admin</a> page
45 will reload in 10 seconds."""
47 SETTINGS2 = """<h3>Your Settings have been saved.</h3> <br>
48 Your settings have been saved to the pyTivo.conf file. pyTivo will now
49 do a <b>Soft Reset</b> to allow these changes to take effect.<br>
50 The <a href="/TiVoConnect?last_page=NPL&Command=Reset&Container=%s">Reset</a>
51 will occur in 2 seconds."""
53 TRANS_INIT = """<h3>Transfer Initiated.</h3> <br>
54 You selected transfer has been initiated.<br>
55 The <a href="/TiVoConnect?Command=%s&Container=%s&TiVo=%s">ToGo</a> page
56 will reload in 3 seconds."""
58 TRANS_STOP = """<h3>Transfer Stopped.</h3> <br>
59 Your transfer has been stopped.<br>
60 The <a href="/TiVoConnect?Command=%s&Container=%s&TiVo=%s">ToGo</a> page
61 will reload in 3 seconds."""
63 UNABLE = """<h3>Unable to Connect to TiVo.</h3> <br>
64 pyTivo was unable to connect to the TiVo at %s</br>
65 This most likely caused by an incorrect Media Access Key. Please return
66 to the ToGo page and double check your Media Access Key.<br>
67 The <a href="/TiVoConnect?Command=NPL&Container=%s">ToGo</a> page will
68 reload in 20 seconds."""
70 # Preload the templates
71 trname = os.path.join(SCRIPTDIR, 'templates', 'redirect.tmpl')
72 tsname = os.path.join(SCRIPTDIR, 'templates', 'settings.tmpl')
73 tnname = os.path.join(SCRIPTDIR, 'templates', 'npl.tmpl')
74 REDIRECT_TEMPLATE = file(trname, 'rb').read()
75 SETTINGS_TEMPLATE = file(tsname, 'rb').read()
76 NPL_TEMPLATE = file(tnname, 'rb').read()
78 # Something to strip
79 TRIBUNE_CR = ' Copyright Tribune Media Services, Inc.'
81 p = os.path.dirname(__file__)
82 p = p.split(os.path.sep)
83 p.pop()
84 p.pop()
85 p = os.path.sep.join(p)
86 config_file_path = os.path.join(p, 'pyTivo.conf')
88 status = {} #Global variable to control download threads
89 tivo_cache = {} #Cache of TiVo NPL
91 def tag_data(element, tag):
92 for name in tag.split('/'):
93 new_element = element.getElementsByTagName(name)
94 if not new_element:
95 return ''
96 element = new_element[0]
97 return element.firstChild.data
99 class Admin(Plugin):
100 CONTENT_TYPE = 'text/html'
102 def Reset(self, handler, query):
103 config.reset()
104 handler.server.reset()
105 if 'last_page' in query:
106 last_page = query['last_page'][0]
107 else:
108 last_page = 'Admin'
110 subcname = query['Container'][0]
111 cname = subcname.split('/')[0]
112 handler.send_response(200)
113 handler.end_headers()
114 t = Template(REDIRECT_TEMPLATE)
115 t.container = cname
116 t.time = '3'
117 t.url = '/TiVoConnect?Command='+ last_page +'&Container=' + quote(cname)
118 t.text = RESET_MSG % (quote(last_page), quote(cname))
119 handler.wfile.write(t)
120 logging.getLogger('pyTivo.admin').info('pyTivo has been soft reset.')
122 def Admin(self, handler, query):
123 #Read config file new each time in case there was any outside edits
124 config = ConfigParser.ConfigParser()
125 config.read(config_file_path)
127 shares_data = []
128 for section in config.sections():
129 if not section.startswith(('_tivo_', 'Server')):
130 if (not(config.has_option(section,'type')) or
131 config.get(section, 'type').lower() != 'admin'):
132 shares_data.append((section,
133 dict(config.items(section, raw=True))))
135 subcname = query['Container'][0]
136 cname = subcname.split('/')[0]
137 handler.send_response(200)
138 handler.end_headers()
139 t = Template(SETTINGS_TEMPLATE)
140 t.container = cname
141 t.quote = quote
142 t.server_data = dict(config.items('Server', raw=True))
143 t.server_known = buildhelp.getknown('server')
144 t.shares_data = shares_data
145 t.shares_known = buildhelp.getknown('shares')
146 t.tivos_data = [(section, dict(config.items(section, raw=True)))
147 for section in config.sections()
148 if section.startswith('_tivo_')]
149 t.tivos_known = buildhelp.getknown('tivos')
150 t.help_list = buildhelp.gethelp()
151 handler.wfile.write(t)
153 def UpdateSettings(self, handler, query):
154 config = ConfigParser.ConfigParser()
155 config.read(config_file_path)
156 for key in query:
157 if key.startswith('Server.'):
158 section, option = key.split('.')
159 if option == "new__setting":
160 new_setting = query[key][0]
161 elif option == "new__value":
162 new_value = query[key][0]
163 elif query[key][0] == " ":
164 config.remove_option(section, option)
165 else:
166 config.set(section, option, query[key][0])
167 if not(new_setting == ' ' and new_value == ' '):
168 config.set('Server', new_setting, new_value)
170 sections = query['Section_Map'][0].split(']')
171 sections.pop() #last item is junk
172 for section in sections:
173 ID, name = section.split('|')
174 if query[ID][0] == "Delete_Me":
175 config.remove_section(name)
176 continue
177 if query[ID][0] != name:
178 config.remove_section(name)
179 config.add_section(query[ID][0])
180 for key in query:
181 if key.startswith(ID + '.'):
182 junk, option = key.split('.')
183 if option == "new__setting":
184 new_setting = query[key][0]
185 elif option == "new__value":
186 new_value = query[key][0]
187 elif query[key][0] == " ":
188 config.remove_option(query[ID][0], option)
189 else:
190 config.set(query[ID][0], option, query[key][0])
191 if not(new_setting == ' ' and new_value == ' '):
192 config.set(query[ID][0], new_setting, new_value)
193 if query['new_Section'][0] != " ":
194 config.add_section(query['new_Section'][0])
195 f = open(config_file_path, "w")
196 config.write(f)
197 f.close()
199 subcname = query['Container'][0]
200 cname = subcname.split('/')[0]
201 handler.send_response(200)
202 handler.end_headers()
203 t = Template(REDIRECT_TEMPLATE)
204 t.container = cname
205 t.time = '10'
206 t.url = '/TiVoConnect?Command=Admin&Container=' + quote(cname)
207 t.text = SETTINGS1 % quote(cname)
208 handler.wfile.write(t)
210 def NPL(self, handler, query):
211 shows_per_page = 50 #Change this to alter the number of shows returned
212 subcname = query['Container'][0]
213 cname = subcname.split('/')[0]
214 folder = ''
215 AnchorItem = ''
216 AnchorOffset= ''
217 for name, data in config.getShares():
218 if cname == name:
219 tivo_mak = data.get('tivo_mak', '')
220 togo_path = data.get('togo_path', '')
222 if 'TiVo' in query:
223 tivoIP = query['TiVo'][0]
224 theurl = 'https://' + tivoIP + \
225 '/TiVoConnect?Command=QueryContainer&ItemCount=' + \
226 str(shows_per_page) + '&Container=/NowPlaying'
227 if 'Folder' in query:
228 folder += str(query['Folder'][0])
229 theurl += '/' + folder
230 if 'AnchorItem' in query:
231 AnchorItem += str(query['AnchorItem'][0])
232 theurl += '&AnchorItem=' + quote(AnchorItem)
233 if 'AnchorOffset' in query:
234 AnchorOffset += str(query['AnchorOffset'][0])
235 theurl += '&AnchorOffset=' + AnchorOffset
237 r=urllib2.Request(theurl)
238 auth_handler = urllib2.HTTPDigestAuthHandler()
239 auth_handler.add_password('TiVo DVR', tivoIP, 'tivo', tivo_mak)
240 opener = urllib2.build_opener(auth_handler)
241 urllib2.install_opener(opener)
243 if theurl in tivo_cache: #check if we've accessed this page before
244 if (tivo_cache[theurl]['thepage'] == '' or
245 (time.time() - tivo_cache[theurl]['thepage_time']) >= 60):
246 #if page is empty or old then retreive it
247 try:
248 handle = urllib2.urlopen(r)
249 except IOError, e:
250 handler.send_response(200)
251 handler.end_headers()
252 t = Template(REDIRECT_TEMPLATE)
253 t.container = cname
254 t.time = '20'
255 t.url = '/TiVoConnect?Command=NPL&Container=' + \
256 quote(cname)
257 t.text = UNABLE % (tivoIP, quote(cname))
258 handler.wfile.write(t)
259 return
260 tivo_cache[theurl]['thepage'] = handle.read()
261 tivo_cache[theurl]['thepage_time'] = time.time()
262 else: #not in cache
263 try:
264 handle = urllib2.urlopen(r)
265 except IOError, e:
266 handler.send_response(200)
267 handler.end_headers()
268 t = Template(REDIRECT_TEMPLATE)
269 t.container = cname
270 t.time = '20'
271 t.url = '/TiVoConnect?Command=NPL&Container=' + quote(cname)
272 t.text = UNABLE % (tivoIP, quote(cname))
273 handler.wfile.write(t)
274 return
275 tivo_cache[theurl] = {}
276 tivo_cache[theurl]['thepage'] = handle.read()
277 tivo_cache[theurl]['thepage_time'] = time.time()
279 xmldoc = minidom.parseString(tivo_cache[theurl]['thepage'])
280 items = xmldoc.getElementsByTagName('Item')
281 TotalItems = tag_data(xmldoc, 'Details/TotalItems')
282 ItemStart = tag_data(xmldoc, 'ItemStart')
283 ItemCount = tag_data(xmldoc, 'ItemCount')
284 FirstAnchor = tag_data(items[0], 'Links/Content/Url')
286 data = []
287 for item in items:
288 entry = {}
289 entry['Title'] = tag_data(item, 'Title')
290 entry['ContentType'] = tag_data(item, 'ContentType')
291 for tag in ('CopyProtected', 'UniqueId'):
292 value = tag_data(item, tag)
293 if value:
294 entry[tag] = value
295 if entry['ContentType'] == 'x-tivo-container/folder':
296 entry['TotalItems'] = tag_data(item, 'TotalItems')
297 lc = int(tag_data(item, 'LastChangeDate'), 16)
298 entry['LastChangeDate'] = time.strftime('%b %d, %Y',
299 time.localtime(lc))
300 else:
301 icon = tag_data(item, 'Links/CustomIcon/Url')
302 if icon:
303 entry['Icon'] = icon
304 url = tag_data(item, 'Links/Content/Url')
305 if url:
306 parse_url = urlparse(url)
307 entry['Url'] = quote('http://%s%s?%s' %
308 (parse_url[1].split(':')[0],
309 parse_url[2], parse_url[4]))
310 keys = ('SourceSize', 'Duration', 'CaptureDate',
311 'EpisodeTitle', 'Description',
312 'SourceChannel', 'SourceStation')
313 for key in keys:
314 entry[key] = tag_data(item, key)
316 entry['SourceSize'] = ( '%.3f GB' %
317 (float(entry['SourceSize']) / (1024 ** 3)) )
319 dur = int(entry['Duration']) / 1000
320 entry['Duration'] = ( '%02d:%02d:%02d' %
321 (dur / 3600, (dur % 3600) / 60, dur % 60) )
323 entry['CaptureDate'] = time.strftime('%b %d, %Y',
324 time.localtime(int(entry['CaptureDate'], 16)))
326 desc = entry['Description']
327 entry['Description'] = desc.replace(TRIBUNE_CR, '')
329 data.append(entry)
330 else:
331 data = []
332 tivoIP = ''
333 TotalItems = 0
334 ItemStart = 0
335 ItemCount = 0
336 FirstAnchor = ''
338 subcname = query['Container'][0]
339 cname = subcname.split('/')[0]
340 handler.send_response(200)
341 handler.send_header('Content-Type', 'text/html; charset=UTF-8')
342 handler.end_headers()
343 t = Template(NPL_TEMPLATE)
344 t.quote = quote
345 t.folder = folder
346 t.status = status
347 t.tivo_mak = tivo_mak
348 t.togo_path = togo_path
349 t.tivos = handler.tivos
350 t.tivo_names = handler.tivo_names
351 t.tivoIP = tivoIP
352 t.container = cname
353 t.data = data
354 t.unquote = unquote
355 t.len = len
356 t.TotalItems = int(TotalItems)
357 t.ItemStart = int(ItemStart)
358 t.ItemCount = int(ItemCount)
359 t.FirstAnchor = quote(FirstAnchor)
360 t.shows_per_page = shows_per_page
361 handler.wfile.write(unicode(t).encode('utf-8'))
363 def get_tivo_file(self, url, mak, tivoIP, outfile):
364 #global status
365 cj = cookielib.LWPCookieJar()
367 r=urllib2.Request(url)
368 auth_handler = urllib2.HTTPDigestAuthHandler()
369 auth_handler.add_password('TiVo DVR', tivoIP, 'tivo', mak)
370 opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj),
371 auth_handler)
372 urllib2.install_opener(opener)
374 try:
375 handle = urllib2.urlopen(r)
376 except IOError, e:
377 #If we get "Too many transfers error" try a second time.
378 #For some reason urllib2 does not properly close connections
379 #when a transfer is canceled.
380 if e.code == 503:
381 try:
382 handle = urllib2.urlopen(r)
383 except IOError, e:
384 status[url]['running'] = False
385 status[url]['error'] = e.code
386 return
387 else:
388 status[url]['running'] = False
389 status[url]['error'] = e.code
390 return
392 f = open(outfile, 'wb')
393 kilobytes = 0
394 start_time = time.time()
395 output = handle.read(1024)
396 while status[url]['running'] and output != '':
397 kilobytes += 1
398 f.write(output)
399 if ((time.time() - start_time) >= 5):
400 status[url]['rate'] = int(kilobytes/(time.time() - start_time))
401 kilobytes = 0
402 start_time = time.time()
403 output = handle.read(1024)
404 status[url]['running'] = False
405 handle.close()
406 f.close()
407 return
409 def ToGo(self, handler, query):
410 subcname = query['Container'][0]
411 cname = subcname.split('/')[0]
412 tivoIP = query['TiVo'][0]
413 for name, data in config.getShares():
414 if cname == name:
415 tivo_mak = data.get('tivo_mak', '')
416 togo_path = data.get('togo_path', '')
417 if tivo_mak and togo_path:
418 parse_url = urlparse(str(query['Url'][0]))
419 theurl = 'http://%s%s?%s' % (parse_url[1].split(':')[0],
420 parse_url[2], parse_url[4])
421 name = unquote(parse_url[2])[10:300].split('.')
422 name.insert(-1," - " + unquote(parse_url[4]).split("id=")[1] + ".")
423 outfile = os.path.join(togo_path, "".join(name))
425 status[theurl] = {'running': True, 'error': '', 'rate': '',
426 'finished': False}
428 thread.start_new_thread(Admin.get_tivo_file,
429 (self, theurl, tivo_mak, tivoIP, outfile))
431 handler.send_response(200)
432 handler.end_headers()
433 t = Template(REDIRECT_TEMPLATE)
434 command = query['Redirect'][0]
435 t.time = '3'
436 t.url = '/TiVoConnect?Command=' + command + '&Container=' + \
437 quote(cname) + '&TiVo=' + tivoIP
438 t.text = TRANS_INIT % (command, quote(cname), tivoIP)
439 handler.wfile.write(t)
440 else:
441 handler.send_response(200)
442 handler.end_headers()
443 t = Template(REDIRECT_TEMPLATE)
444 command = query['Redirect'][0]
445 t.time = '10'
446 t.url = '/TiVoConnect?Command=' + command + '&Container=' + \
447 quote(cname) + '&TiVo=' + tivoIP
448 t.text = MISSING % (command, quote(cname), tivoIP)
449 handler.wfile.write(t)
451 def ToGoStop(self, handler, query):
452 parse_url = urlparse(str(query['Url'][0]))
453 theurl = 'http://%s%s?%s' % (parse_url[1].split(':')[0],
454 parse_url[2], parse_url[4])
456 status[theurl]['running'] = False
458 subcname = query['Container'][0]
459 cname = subcname.split('/')[0]
460 tivoIP = query['TiVo'][0]
461 command = query['Redirect'][0]
462 handler.send_response(200)
463 handler.end_headers()
464 t = Template(REDIRECT_TEMPLATE)
465 t.time = '3'
466 t.url = '/TiVoConnect?Command=' + command + '&Container=' + \
467 quote(cname) + '&TiVo=' + tivoIP
468 t.text = TRANS_STOP % (command, quote(cname), tivoIP)
469 handler.wfile.write(t)
472 def SaveNPL(self, handler, query):
473 config = ConfigParser.ConfigParser()
474 config.read(config_file_path)
475 if 'tivo_mak' in query:
476 config.set(query['Container'][0], 'tivo_mak',
477 query['tivo_mak'][0])
478 if 'togo_path' in query:
479 config.set(query['Container'][0], 'togo_path',
480 query['togo_path'][0])
481 f = open(config_file_path, "w")
482 config.write(f)
483 f.close()
485 subcname = query['Container'][0]
486 cname = subcname.split('/')[0]
487 handler.send_response(200)
488 handler.end_headers()
489 t = Template(REDIRECT_TEMPLATE)
490 t.container = cname
491 t.time = '2'
492 t.url = '/TiVoConnect?last_page=NPL&Command=Reset&Container=' + \
493 quote(cname)
494 t.text = SETTINGS2 % quote(cname)
495 handler.wfile.write(t)