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