import cleanup
[vuplus_dvbapp-plugin] / webinterface / src / webif.py
1 Version = '$Header$';
2
3 # OK, this is more than a proof of concept
4 # things to improve:
5 #  - nicer code
6 #  - screens need to be defined somehow else. 
7 #    I don't know how, yet. Probably each in an own file.
8 #  - more components, like the channellist
9 #  - better error handling
10 #  - use namespace parser
11 from enigma import eServiceReference
12
13 from Screens.Screen import Screen
14 from Tools.Import import my_import
15
16 from Screens.InfoBarGenerics import InfoBarServiceName, InfoBarEvent, InfoBarTuner
17
18 from Components.Sources.Clock import Clock
19 from Components.Sources.ServiceList import ServiceList
20
21 from WebComponents.Sources.ServiceListRecursive import ServiceListRecursive
22 from WebComponents.Sources.Volume import Volume
23 from WebComponents.Sources.EPG import EPG
24 from WebComponents.Sources.Timer import Timer
25 from WebComponents.Sources.Movie import Movie
26 from WebComponents.Sources.Message import Message
27 from WebComponents.Sources.PowerState import PowerState
28 from WebComponents.Sources.RemoteControl import RemoteControl
29 from WebComponents.Sources.Settings import Settings
30 from WebComponents.Sources.SubServices import SubServices
31 from WebComponents.Sources.ParentControl import ParentControl
32 from WebComponents.Sources.About import About
33 from WebComponents.Sources.RequestData import RequestData
34 from WebComponents.Sources.AudioTracks import AudioTracks
35 from WebComponents.Sources.WAPfunctions import WAPfunctions
36 from WebComponents.Sources.MP import MP
37 from WebComponents.Sources.Files import Files
38
39 from Components.Sources.FrontendStatus import FrontendStatus
40
41 from Components.Converter.Converter import Converter
42
43 from Components.Element import Element
44
45 from xml.sax import make_parser
46 from xml.sax.handler import ContentHandler, feature_namespaces
47
48 from twisted.python import util
49
50 # prototype of the new web frontend template system.
51
52 class WebScreen(Screen):
53         def __init__(self, session, request):
54                 Screen.__init__(self, session)
55                 self.stand_alone = True
56                 self.request = request
57                 self.instance = None
58                 
59 class DummyWebScreen(WebScreen):
60         #use it, if you dont need any source, just to can do a static file with an xml-file
61         def __init__(self, session,request):
62                 WebScreen.__init__(self, session,request)
63
64 class UpdateWebScreen(InfoBarServiceName, InfoBarEvent,InfoBarTuner,WebScreen):
65         def __init__(self, session,request):
66                 WebScreen.__init__(self, session,request)
67                 InfoBarServiceName.__init__(self)
68                 InfoBarEvent.__init__(self)
69                 InfoBarTuner.__init__(self)
70                 self["CurrentTime"] = Clock()
71                 fav = eServiceReference('1:7:1:0:0:0:0:0:0:0:(type == 1) || (type == 17) || (type == 195) || (type == 25) FROM BOUQUET "bouquets.tv" ORDER BY bouquet')
72                 #CurrentService
73                 #Event_Now
74                 #Event_Next
75                 #FrontendStatus
76                 
77 class MessageWebScreen(WebScreen):
78         def __init__(self, session,request):
79                 WebScreen.__init__(self, session,request)
80                 self["Message"] = Message(session,func = Message.PRINT)
81                 self["GetAnswer"] = Message(session,func = Message.ANSWER)
82
83 class AudioWebScreen(WebScreen):
84         def __init__(self, session,request):
85                 WebScreen.__init__(self, session,request)
86                 self["AudioTracks"] = AudioTracks(session)              
87
88 class AboutWebScreen(WebScreen):
89         def __init__(self, session,request):
90                 WebScreen.__init__(self, session,request)
91                 self["About"] = About(session)
92                 
93 class VolumeWebScreen(WebScreen):
94         def __init__(self, session,request):
95                 WebScreen.__init__(self, session,request)
96                 self["Volume"] = Volume(session)
97
98 class SettingsWebScreen(WebScreen):
99         def __init__(self, session,request):
100                 WebScreen.__init__(self, session,request)
101                 self["Settings"] = Settings(session)
102
103 class SubServiceWebScreen(WebScreen):
104         def __init__(self, session,request):
105                 WebScreen.__init__(self, session,request)
106                 self["SubServices"] = SubServices(session)
107
108 class ServiceWebScreen(WebScreen):
109         def __init__(self, session,request):
110                 WebScreen.__init__(self, session,request)
111                 fav = eServiceReference('1:7:1:0:0:0:0:0:0:0:(type == 1) || (type == 17) || (type == 195) || (type == 25) FROM BOUQUET "bouquets.tv" ORDER BY bouquet')
112                 self["SwitchService"] = ServiceList(fav, command_func = self.zapTo, validate_commands=False)
113                 self["ServiceList"] = ServiceList(fav, command_func = self.getServiceList, validate_commands=False)
114                 self["ServiceListRecursive"] = ServiceListRecursive(session, func=ServiceListRecursive.FETCH)
115
116         def getServiceList(self, sRef):
117                 self["ServiceList"].root = sRef
118
119         def zapTo(self, reftozap):
120                 from Components.config import config
121                 pc = config.ParentalControl.configured.value
122                 if pc:
123                         config.ParentalControl.configured.value = False
124                 self.session.nav.playService(reftozap)
125                 if pc:
126                         config.ParentalControl.configured.value = pc
127                 """
128                 switching config.ParentalControl.configured.value
129                 ugly, but necessary :(
130                 """
131
132 class EPGWebScreen(WebScreen):
133         def __init__(self, session,request):
134                 WebScreen.__init__(self, session,request)
135                 self["EPGTITLE"] = EPG(session,func=EPG.TITLE)
136                 self["EPGSERVICE"] = EPG(session,func=EPG.SERVICE)
137                 self["EPGNOW"] = EPG(session,func=EPG.NOW)
138
139 class MovieWebScreen(WebScreen):
140         def __init__(self, session,request):
141                 WebScreen.__init__(self, session,request)
142                 from Components.MovieList import MovieList
143                 from Tools.Directories import resolveFilename,SCOPE_HDD
144                 movielist = MovieList(eServiceReference("2:0:1:0:0:0:0:0:0:0:" + resolveFilename(SCOPE_HDD)))
145                 self["MovieList"] = Movie(session,movielist,func = Movie.LIST)
146                 self["MovieFileDel"] = Movie(session,movielist,func = Movie.DEL)
147                 self["MovieTags"] = Movie(session,movielist,func = Movie.TAGS)
148
149 class MediaPlayerWebScreen(WebScreen):
150         def __init__(self, session,request):
151                 WebScreen.__init__(self, session,request)
152                 self["FileList"] = MP(session,func = MP.LIST)
153                 self["PlayFile"] = MP(session,func = MP.PLAY)
154                 self["Command"] = MP(session,func = MP.COMMAND)
155                 
156 class FilesWebScreen(WebScreen):
157         def __init__(self, session,request):
158                 WebScreen.__init__(self, session,request)
159                 self["DelFile"] = Files(session,func = Files.DEL)
160                 
161 class TimerWebScreen(WebScreen):
162         def __init__(self, session,request):
163                 WebScreen.__init__(self, session,request)
164                 self["TimerList"] = Timer(session,func = Timer.LIST)
165                 self["TimerAddEventID"] = Timer(session,func = Timer.ADDBYID)
166                 self["TimerAdd"] = Timer(session,func = Timer.ADD)
167                 self["TimerDel"] = Timer(session,func = Timer.DEL)
168                 self["TimerChange"] = Timer(session,func = Timer.CHANGE)
169                 self["TimerListWrite"] = Timer(session,func = Timer.WRITE)
170                 self["TVBrowser"] = Timer(session,func = Timer.TVBROWSER)
171                 self["RecordNow"] = Timer(session,func = Timer.RECNOW)
172
173 class RemoteWebScreen(WebScreen):
174         def __init__(self, session,request):
175                 WebScreen.__init__(self, session,request)
176                 self["RemoteControl"] = RemoteControl(session)
177
178 class PowerWebScreen(WebScreen):
179         def __init__(self, session,request):
180                 WebScreen.__init__(self, session,request)
181                 self["PowerState"] = PowerState(session)
182
183 class ParentControlWebScreen(WebScreen):
184         def __init__(self, session,request):
185                 WebScreen.__init__(self, session,request)
186                 self["ParentControlList"] = ParentControl(session)
187                                 
188 class WAPWebScreen(WebScreen):
189         def __init__(self, session,request):
190                 WebScreen.__init__(self, session,request)
191                 self["WAPFillOptionListSyear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
192                 self["WAPFillOptionListSday"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
193                 self["WAPFillOptionListSmonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
194                 self["WAPFillOptionListShour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
195                 self["WAPFillOptionListSmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
196                 
197                 self["WAPFillOptionListEyear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
198                 self["WAPFillOptionListEday"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
199                 self["WAPFillOptionListEmonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
200                 self["WAPFillOptionListEhour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
201                 self["WAPFillOptionListEmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
202                 
203                 self["WAPFillOptionListRecord"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
204                 self["WAPFillOptionListAfterEvent"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
205                 
206                 self["WAPFillValueName"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
207                 self["WAPFillValueDescr"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
208
209                 self["WAPFillOptionListRepeated"] = WAPfunctions(session,func = WAPfunctions.REPEATED)
210                 self["WAPServiceList"] = WAPfunctions(session, func = WAPfunctions.SERVICELIST)
211
212                 self["WAPdeleteOldOnSave"] = WAPfunctions(session,func = WAPfunctions.DELETEOLD)
213         
214 class StreamingWebScreen(WebScreen):
215         def __init__(self, session,request):
216                 WebScreen.__init__(self, session,request)
217                 from Components.Sources.StreamService import StreamService
218                 self["StreamService"] = StreamService(self.session.nav)
219
220 class M3UStreamingWebScreen(WebScreen):
221         def __init__(self, session,request):
222                 WebScreen.__init__(self, session,request)
223                 from Components.Sources.StaticText import StaticText
224                 from Components.Sources.Config import Config
225                 from Components.config import config
226                 self["ref"] = StaticText()
227                 self["localip"] = RequestData(request,what=RequestData.HOST)
228
229 class TsM3U(WebScreen):
230         def __init__(self, session,request):
231                 WebScreen.__init__(self, session,request)
232                 from Components.Sources.StaticText import StaticText
233                 from Components.Sources.Config import Config
234                 from Components.config import config
235                 self["file"] = StaticText()
236                 self["localip"] = RequestData(request,what=RequestData.HOST)
237
238 class RestartWebScreen(WebScreen):
239         def __init__(self, session,request):
240                 WebScreen.__init__(self, session,request)
241                 import plugin
242                 plugin.restartWebserver()
243                 
244 class GetPid(WebScreen):
245       def __init__(self, session,request):
246          WebScreen.__init__(self, session,request)
247          from Components.Sources.StaticText import StaticText
248          from enigma import iServiceInformation
249          pids = self.session.nav.getCurrentService()
250          if pids is not None:
251                  pidinfo = pids.info()
252                  VPID = hex(pidinfo.getInfo(iServiceInformation.sVideoPID))
253                  APID = hex(pidinfo.getInfo(iServiceInformation.sAudioPID))
254                  PPID = hex(pidinfo.getInfo(iServiceInformation.sPMTPID))
255          self["pids"] = StaticText("%s,%s,%s"%(PPID.lstrip("0x"),VPID.lstrip("0x"),APID.lstrip("0x")))
256          self["localip"] = RequestData(request,what=RequestData.HOST)
257
258
259 # implements the 'render'-call.
260 # this will act as a downstream_element, like a renderer.
261 class OneTimeElement(Element):
262         def __init__(self, id):
263                 Element.__init__(self)
264                 self.source_id = id
265
266         # CHECKME: is this ok performance-wise?
267         def handleCommand(self, args):
268                 if self.source_id.find(",") >=0:
269                         paramlist = self.source_id.split(",")
270                         list={}
271                         for key in paramlist:
272                                 arg = args.get(key, [])
273                                 if len(arg) == 0:
274                                         list[key] = None        
275                                 elif len(arg) == 1:
276                                         list[key] = "".join(arg)        
277                                 elif len(arg) == 2:
278                                         list[key] = arg[0]
279                         self.source.handleCommand(list)
280                 else:
281                         for c in args.get(self.source_id, []):
282                                 self.source.handleCommand(c)
283                 
284         def render(self, stream):
285                 t = self.source.getHTML(self.source_id)
286                 stream.write(t)
287
288         def execBegin(self):
289                 pass
290         
291         def execEnd(self):
292                 pass
293         
294         def onShow(self):
295                 pass
296
297         def onHide(self):
298                 pass
299         
300         def destroy(self):
301                 pass
302
303 class StreamingElement(OneTimeElement):
304         def __init__(self, id):
305                 OneTimeElement.__init__(self, id)
306                 self.stream = None
307
308         def changed(self, what):
309                 if self.stream:
310                         self.render(self.stream)
311
312         def setStream(self, stream):
313                 self.stream = stream
314
315 # a to-be-filled list item
316 class ListItem:
317         def __init__(self, name, filternum):
318                 self.name = name
319                 self.filternum = filternum
320         
321 class TextToHTML(Converter):
322         def __init__(self, arg):
323                 Converter.__init__(self, arg)
324
325         def getHTML(self, id):
326                 return self.source.text # encode & etc. here!
327
328 class TextToURL(Converter):
329         def __init__(self, arg):
330                 Converter.__init__(self, arg)
331
332         def getHTML(self, id):
333                 return self.source.text.replace(" ","%20")
334
335 class ReturnEmptyXML(Converter):
336         def __init__(self, arg):
337                 Converter.__init__(self, arg)
338                 
339         def getHTML(self, id):
340                 return "<rootElement></rootElement>"
341
342 # a null-output. Useful if you only want to issue a command.
343 class Null(Converter):
344         def __init__(self, arg):
345                 Converter.__init__(self, arg)
346
347         def getHTML(self, id):
348                 return ""
349
350 class JavascriptUpdate(Converter):
351         def __init__(self, arg):
352                 Converter.__init__(self, arg)
353
354         def getHTML(self, id):
355                 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
356                 #                all other will replace this in JS
357                 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '&deg;'))
358
359 # the performant 'listfiller'-engine (plfe)
360 class ListFiller(Converter):
361         def __init__(self, arg):
362                 Converter.__init__(self, arg)
363 #               print "ListFiller-arg: ",arg
364
365         def getText(self):
366                 l = self.source.list
367                 lut = self.source.lut
368                 conv_args = self.converter_arguments
369
370                 # now build a ["string", 1, "string", 2]-styled list, with indices into the
371                 # list to avoid lookup of item name for each entry
372                 lutlist = [ isinstance(element, basestring) and (element, None) or (lut[element.name], element.filternum) for element in conv_args ]
373
374                 # now, for the huge list, do:
375                 strlist = [ ]
376                 append = strlist.append
377                 for item in l:
378                         for (element, filternum) in lutlist:
379                                 if not filternum:
380                                         append(element)
381                                 elif filternum == 2:
382                                         append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
383                                 elif filternum == 3:
384                                         append(str(item[element]).replace("&", "&amp;").replace("<", "&lt;").replace('"', '&quot;').replace(">", "&gt;"))
385                                 elif filternum == 4:
386                                         append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
387                                 else:
388                                         append(str(item[element]))
389                 # (this will be done in c++ later!)
390                 return ''.join(strlist)
391
392         text = property(getText)
393
394 class webifHandler(ContentHandler):
395         def __init__(self, session,request):
396                 self.res = [ ]
397                 self.mode = 0
398                 self.screen = None
399                 self.session = session
400                 self.screens = [ ]
401                 self.request = request
402         
403         def startElement(self, name, attrs):
404                 if name == "e2:screen":
405                         self.screen = eval(attrs["name"])(self.session,self.request) # fixme
406                         self.screens.append(self.screen)
407                         return
408         
409                 if name[:3] == "e2:":
410                         self.mode += 1
411
412                 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
413                 tag.insert(0, name)
414                 tag.insert(0, '<')
415                 tag.append('>')
416                 tag = ''.join(tag)#.encode('utf-8')
417
418                 if self.mode == 0:
419                         self.res.append(tag)
420                 elif self.mode == 1: # expect "<e2:element>"
421                         assert name == "e2:element", "found %s instead of e2:element" % name
422                         source = attrs["source"]
423                         self.source_id = str(attrs.get("id", source))
424                         self.source = self.screen[source]
425                         self.is_streaming = "streaming" in attrs
426                 elif self.mode == 2: # expect "<e2:convert>"
427                         if name[:3] == "e2:":
428                                 assert name == "e2:convert"
429                                 
430                                 ctype = attrs["type"]
431                                 
432                                         # TODO: we need something better here
433                                 if ctype[:4] == "web:": # for now
434                                         self.converter = eval(ctype[4:])
435                                 else:
436                                         try:
437                                                 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
438                                         except ImportError:
439                                                 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
440                                 self.sub = [ ]
441                         else:
442                                 self.sub.append(tag)
443                 elif self.mode == 3:
444                         assert name == "e2:item", "found %s instead of e2:item!" % name
445                         assert "name" in attrs, "e2:item must have a name= attribute!"
446                         filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4}[attrs.get("filter", "")]
447                         self.sub.append(ListItem(attrs["name"], filter))
448
449         def endElement(self, name):
450                 if name == "e2:screen":
451                         self.screen = None
452                         return
453
454                 tag = "</" + name + ">"
455                 if self.mode == 0:
456                         self.res.append(tag)
457                 elif self.mode == 2 and name[:3] != "e2:":
458                         self.sub.append(tag)
459                 elif self.mode == 2: # closed 'convert' -> sub
460                         if len(self.sub) == 1:
461                                 self.sub = self.sub[0]
462                         c = self.converter(self.sub)
463                         c.connect(self.source)
464                         self.source = c
465                         del self.sub
466                 elif self.mode == 1: # closed 'element'
467                         # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
468                         if not self.is_streaming:
469                                 c = OneTimeElement(self.source_id)
470                         else:
471                                 c = StreamingElement(self.source_id)
472                         
473                         c.connect(self.source)
474                         self.res.append(c)
475                         self.screen.renderer.append(c)
476                         del self.source
477
478                 if name[:3] == "e2:":
479                         self.mode -= 1
480
481         def processingInstruction(self, target, data):
482                 self.res.append('<?' + target + ' ' + data + '>')
483         
484         def characters(self, ch):
485                 ch = ch.encode('utf-8')
486                 if self.mode == 0:
487                         self.res.append(ch)
488                 elif self.mode == 2:
489                         self.sub.append(ch)
490         
491         def startEntity(self, name):
492                 self.res.append('&' + name + ';');
493
494         def execBegin(self):
495                 for screen in self.screens:
496                         screen.execBegin()
497
498         def cleanup(self):
499                 print "screen cleanup!"
500                 for screen in self.screens:
501                         screen.execEnd()
502                         screen.doClose()
503                 self.screens = [ ]
504
505 def renderPage(stream, path, req, session):
506         
507         # read in the template, create required screens
508         # we don't have persistense yet.
509         # if we had, this first part would only be done once.
510         handler = webifHandler(session,req)
511         parser = make_parser()
512         parser.setFeature(feature_namespaces, 0)
513         parser.setContentHandler(handler)
514         parser.parse(open(util.sibpath(__file__, path)))
515         
516         # by default, we have non-streaming pages
517         finish = True
518         
519         # first, apply "commands" (aka. URL argument)
520         for x in handler.res:
521                 if isinstance(x, Element):
522                         x.handleCommand(req.args)
523
524         handler.execBegin()
525
526         # now, we have a list with static texts mixed
527         # with non-static Elements.
528         # flatten this list, write into the stream.
529         for x in handler.res:
530                 if isinstance(x, Element):
531                         if isinstance(x, StreamingElement):
532                                 finish = False
533                                 x.setStream(stream)
534                         x.render(stream)
535                 else:
536                         stream.write(str(x))
537
538         def ping(s):
539                 from twisted.internet import reactor
540                 s.write("\n");
541                 reactor.callLater(3, ping, s)
542         
543         # if we met a "StreamingElement", there is at least one
544         # element which wants to output data more than once,
545         # i.e. on host-originated changes.
546         # in this case, don't finish yet, don't cleanup yet,
547         # but instead do that when the client disconnects.
548         if finish:
549                 handler.cleanup()
550                 stream.finish()
551         else:
552                 # ok.
553                 # you *need* something which constantly sends something in a regular interval,
554                 # in order to detect disconnected clients.
555                 # i agree that this "ping" sucks terrible, so better be sure to have something 
556                 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
557                 ping(stream)
558                 stream.closed_callback = lambda: handler.cleanup()