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