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