integraded parental conrtol. still stuff to do in webif.py. ugly switching of config....
[enigma2-plugins.git] / webinterface / src / webif.py
1 #
2 # OK, this is more than a proof of concept
3 # things to improve:
4 #  - nicer code
5 #  - screens need to be defined somehow else. 
6 #    I don't know how, yet. Probably each in an own file.
7 #  - more components, like the channellist
8 #  - better error handling
9 #  - use namespace parser
10
11 from Screens.Screen import Screen
12 from Tools.Import import my_import
13
14 # for our testscreen
15 from Screens.InfoBarGenerics import InfoBarServiceName, InfoBarEvent, InfoBarTuner
16
17 from Components.Sources.Clock import Clock
18 from Components.Sources.ServiceList import ServiceList
19 from WebComponents.Sources.Volume import Volume
20 from WebComponents.Sources.EPG import EPG
21 from WebComponents.Sources.Timer import Timer
22 from WebComponents.Sources.Movie import Movie
23 from WebComponents.Sources.Message import Message
24 from WebComponents.Sources.PowerState import PowerState
25 from WebComponents.Sources.RemoteControl import RemoteControl
26 from WebComponents.Sources.Settings import Settings
27 from WebComponents.Sources.SubServices import SubServices
28 from WebComponents.Sources.ParentControl import ParentControl
29
30 from WebComponents.Sources.RequestData import RequestData
31 from Components.Sources.FrontendStatus import FrontendStatus
32
33 from Components.Converter.Converter import Converter
34
35 from Components.Element import Element
36
37 from xml.sax import make_parser
38 from xml.sax.handler import ContentHandler, feature_namespaces
39 from twisted.python import util
40 import sys
41 import time
42  
43 # prototype of the new web frontend template system.
44
45 class WebScreen(Screen):
46         def __init__(self, session,request):
47                 Screen.__init__(self, session)
48                 self.stand_alone = True
49                 self.request = request
50 # a test screen
51 class TestScreen(InfoBarServiceName, InfoBarEvent,InfoBarTuner, WebScreen):
52         def __init__(self, session,request):
53                 WebScreen.__init__(self, session,request)
54                 InfoBarServiceName.__init__(self)
55                 InfoBarEvent.__init__(self)
56                 InfoBarTuner.__init__(self)
57                 self["CurrentTime"] = Clock()
58 #               self["TVSystem"] = Config(config.av.tvsystem)
59 #               self["OSDLanguage"] = Config(config.osd.language)
60 #               self["FirstRun"] = Config(config.misc.firstrun)
61                 from enigma import eServiceReference
62                 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')
63                 self["ServiceList"] = ServiceList(fav, command_func = self.zapTo, validate_commands=False)
64                 self["ServiceListBrowse"] = ServiceList(fav, command_func = self.browseTo, validate_commands=False)
65
66                 self["ParentControlList"] = ParentControl(session)
67                 self["SubServices"] = SubServices(session)
68                 self["Volume"] = Volume(session)
69                 self["EPGTITLE"] = EPG(session,func=EPG.TITLE)
70                 self["EPGSERVICE"] = EPG(session,func=EPG.SERVICE)
71                 self["EPGNOW"] = EPG(session,func=EPG.NOW)
72                 self["TimerList"] = Timer(session,func = Timer.LIST)
73                 self["TimerAddEventID"] = Timer(session,func = Timer.ADDBYID)
74                 self["TimerAdd"] = Timer(session,func = Timer.ADD)
75                 self["TimerDel"] = Timer(session,func = Timer.DEL)
76                 self["TimerChange"] = Timer(session,func = Timer.CHANGE)
77                 self["TimerListWrite"] = Timer(session,func = Timer.WRITE)
78                 self["TVBrowser"] = Timer(session,func = Timer.TVBROWSER)
79                 self["MovieList"] = Movie(session,func = Movie.LIST)
80                 self["MovieFileDel"] = Movie(session,func = Movie.DEL)
81                 self["Volume"] = Volume(session)
82                 self["Message"] = Message(session)
83                 self["PowerState"] = PowerState(session)
84                 self["RemoteControl"] = RemoteControl(session)
85                 self["Settings"] = Settings(session)
86                 
87         def browseTo(self, reftobrowse):
88                 self["ServiceListBrowse"].root = reftobrowse
89
90         def zapTo(self, reftozap):
91                 from Components.config import config
92                 pc = config.ParentalControl.configured.value
93                 if pc:
94                         config.ParentalControl.configured.value = False
95                 self.session.nav.playService(reftozap)
96                 if pc:
97                         config.ParentalControl.configured.value = pc
98                 """
99                 switsching config.ParentalControl.configured.value
100                 ugly, but necessary :(
101                 """
102
103 # TODO: (really.) put screens into own files.
104 class Streaming(WebScreen):
105         def __init__(self, session,request):
106                 WebScreen.__init__(self, session,request)
107                 from Components.Sources.StreamService import StreamService
108                 self["StreamService"] = StreamService(self.session.nav)
109
110 class StreamingM3U(WebScreen):
111         def __init__(self, session,request):
112                 WebScreen.__init__(self, session,request)
113                 from Components.Sources.StaticText import StaticText
114                 from Components.Sources.Config import Config
115                 from Components.config import config
116                 self["ref"] = StaticText()
117                 self["localip"] = RequestData(request,what=RequestData.HOST)
118
119 class TsM3U(WebScreen):
120         def __init__(self, session,request):
121                 WebScreen.__init__(self, session,request)
122                 from Components.Sources.StaticText import StaticText
123                 from Components.Sources.Config import Config
124                 from Components.config import config
125                 self["file"] = StaticText()
126                 self["localip"] = RequestData(request,what=RequestData.HOST)
127                 
128 class RestartTwisted(WebScreen):
129         def __init__(self, session,request):
130                 WebScreen.__init__(self, session,request)
131                 import plugin
132                 plugin.restartWebserver()
133                 
134 class GetPid(WebScreen):
135       def __init__(self, session,request):
136          WebScreen.__init__(self, session,request)
137          from Components.Sources.StaticText import StaticText
138          from enigma import iServiceInformation
139          pids = self.session.nav.getCurrentService()
140          if pids is not None:
141                  pidinfo = pids.info()
142                  VPID = hex(pidinfo.getInfo(iServiceInformation.sVideoPID))
143                  APID = hex(pidinfo.getInfo(iServiceInformation.sAudioPID))
144                  PPID = hex(pidinfo.getInfo(iServiceInformation.sPMTPID))
145          self["pids"] = StaticText("%s,%s,%s"%(PPID.lstrip("0x"),VPID.lstrip("0x"),APID.lstrip("0x")))
146          self["localip"] = RequestData(request,what=RequestData.HOST)
147
148
149 # implements the 'render'-call.
150 # this will act as a downstream_element, like a renderer.
151 class OneTimeElement(Element):
152         def __init__(self, id):
153                 Element.__init__(self)
154                 self.source_id = id
155
156         # CHECKME: is this ok performance-wise?
157         def handleCommand(self, args):
158                 if self.source_id.find(",") >=0:
159                         paramlist = self.source_id.split(",")
160                         list={}
161                         for key in paramlist:
162                                 arg = args.get(key, [])
163                                 if len(arg) == 0:
164                                         list[key] = None        
165                                 elif len(arg) == 1:
166                                         list[key] = "".join(arg)        
167                                 elif len(arg) == 2:
168                                         list[key] = arg[0]
169                         self.source.handleCommand(list)
170                 else:
171                         for c in args.get(self.source_id, []):
172                                 self.source.handleCommand(c)
173                 
174         def render(self, stream):
175                 t = self.source.getHTML(self.source_id)
176                 stream.write(t)
177
178         def execBegin(self):
179                 pass
180         
181         def execEnd(self):
182                 pass
183         
184         def onShow(self):
185                 pass
186
187         def onHide(self):
188                 pass
189         
190         def destroy(self):
191                 pass
192
193 class StreamingElement(OneTimeElement):
194         def __init__(self, id):
195                 OneTimeElement.__init__(self, id)
196                 self.stream = None
197
198         def changed(self, what):
199                 if self.stream:
200                         self.render(self.stream)
201
202         def setStream(self, stream):
203                 self.stream = stream
204
205 # a to-be-filled list item
206 class ListItem:
207         def __init__(self, name, filternum):
208                 self.name = name
209                 self.filternum = filternum
210         
211 class TextToHTML(Converter):
212         def __init__(self, arg):
213                 Converter.__init__(self, arg)
214
215         def getHTML(self, id):
216                 return self.source.text # encode & etc. here!
217
218 class ReturnEmptyXML(Converter):
219         def __init__(self, arg):
220                 Converter.__init__(self, arg)
221                 
222         def getHTML(self, id):
223                 return "<rootElement></rootElement>"
224
225 # a null-output. Useful if you only want to issue a command.
226 class Null(Converter):
227         def __init__(self, arg):
228                 Converter.__init__(self, arg)
229
230         def getHTML(self, id):
231                 return ""
232
233 class JavascriptUpdate(Converter):
234         def __init__(self, arg):
235                 Converter.__init__(self, arg)
236
237         def getHTML(self, id):
238                 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
239                 #                all other will replace this in JS
240                 print  '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '&deg;'))
241                 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '&deg;'))
242
243 # the performant 'listfiller'-engine (plfe)
244 class ListFiller(Converter):
245         def __init__(self, arg):
246                 Converter.__init__(self, arg)
247
248         def getText(self):
249                 l = self.source.list
250                 lut = self.source.lut
251                 conv_args = self.converter_arguments
252
253                 # now build a ["string", 1, "string", 2]-styled list, with indices into the
254                 # list to avoid lookup of item name for each entry
255                 lutlist = [ isinstance(element, basestring) and (element, None) or (lut[element.name], element.filternum) for element in conv_args ]
256
257                 # now, for the huge list, do:
258                 strlist = [ ]
259                 append = strlist.append
260                 for item in l:
261                         for (element, filternum) in lutlist:
262                                 if not filternum:
263                                         append(element)
264                                 elif filternum == 2:
265                                         append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
266                                 elif filternum == 3:
267                                         append(str(item[element]).replace("&", "&amp;").replace("<", "&lt;").replace('"', '&quot;').replace(">", "&gt;"))
268                                 elif filternum == 4:
269                                         append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
270                                 else:
271                                         append(str(item[element]))
272                 # (this will be done in c++ later!)
273                 return ''.join(strlist)
274
275         text = property(getText)
276
277 class webifHandler(ContentHandler):
278         def __init__(self, session,request):
279                 self.res = [ ]
280                 self.mode = 0
281                 self.screen = None
282                 self.session = session
283                 self.screens = [ ]
284                 self.request = request
285         
286         def startElement(self, name, attrs):
287                 if name == "e2:screen":
288                         self.screen = eval(attrs["name"])(self.session,self.request) # fixme
289                         self.screens.append(self.screen)
290                         return
291         
292                 if name[:3] == "e2:":
293                         self.mode += 1
294
295                 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
296                 tag.insert(0, name)
297                 tag.insert(0, '<')
298                 tag.append('>')
299                 tag = ''.join(tag)#.encode('utf-8')
300
301                 if self.mode == 0:
302                         self.res.append(tag)
303                 elif self.mode == 1: # expect "<e2:element>"
304                         assert name == "e2:element", "found %s instead of e2:element" % name
305                         source = attrs["source"]
306                         self.source_id = str(attrs.get("id", source))
307                         self.source = self.screen[source]
308                         self.is_streaming = "streaming" in attrs
309                 elif self.mode == 2: # expect "<e2:convert>"
310                         if name[:3] == "e2:":
311                                 assert name == "e2:convert"
312                                 
313                                 ctype = attrs["type"]
314                                 
315                                         # TODO: we need something better here
316                                 if ctype[:4] == "web:": # for now
317                                         self.converter = eval(ctype[4:])
318                                 else:
319                                         try:
320                                                 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
321                                         except ImportError:
322                                                 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
323                                 self.sub = [ ]
324                         else:
325                                 self.sub.append(tag)
326                 elif self.mode == 3:
327                         assert name == "e2:item", "found %s instead of e2:item!" % name
328                         assert "name" in attrs, "e2:item must have a name= attribute!"
329                         filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4}[attrs.get("filter", "")]
330                         self.sub.append(ListItem(attrs["name"], filter))
331
332         def endElement(self, name):
333                 if name == "e2:screen":
334                         self.screen = None
335                         return
336
337                 tag = "</" + name + ">"
338                 if self.mode == 0:
339                         self.res.append(tag)
340                 elif self.mode == 2 and name[:3] != "e2:":
341                         self.sub.append(tag)
342                 elif self.mode == 2: # closed 'convert' -> sub
343                         if len(self.sub) == 1:
344                                 self.sub = self.sub[0]
345                         c = self.converter(self.sub)
346                         c.connect(self.source)
347                         self.source = c
348                         del self.sub
349                 elif self.mode == 1: # closed 'element'
350                         # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
351                         if not self.is_streaming:
352                                 c = OneTimeElement(self.source_id)
353                         else:
354                                 c = StreamingElement(self.source_id)
355                         
356                         c.connect(self.source)
357                         self.res.append(c)
358                         self.screen.renderer.append(c)
359                         del self.source
360
361                 if name[:3] == "e2:":
362                         self.mode -= 1
363
364         def processingInstruction(self, target, data):
365                 self.res.append('<?' + target + ' ' + data + '>')
366         
367         def characters(self, ch):
368                 ch = ch.encode('utf-8')
369                 if self.mode == 0:
370                         self.res.append(ch)
371                 elif self.mode == 2:
372                         self.sub.append(ch)
373         
374         def startEntity(self, name):
375                 self.res.append('&' + name + ';');
376
377         def execBegin(self):
378                 for screen in self.screens:
379                         screen.execBegin()
380
381         def cleanup(self):
382                 print "screen cleanup!"
383                 for screen in self.screens:
384                         screen.execEnd()
385                         screen.doClose()
386                 self.screens = [ ]
387
388 def renderPage(stream, path, req, session):
389         
390         # read in the template, create required screens
391         # we don't have persistense yet.
392         # if we had, this first part would only be done once.
393         handler = webifHandler(session,req)
394         parser = make_parser()
395         parser.setFeature(feature_namespaces, 0)
396         parser.setContentHandler(handler)
397         parser.parse(open(util.sibpath(__file__, path)))
398         
399         # by default, we have non-streaming pages
400         finish = True
401         
402         # first, apply "commands" (aka. URL argument)
403         for x in handler.res:
404                 if isinstance(x, Element):
405                         x.handleCommand(req.args)
406
407         handler.execBegin()
408
409         # now, we have a list with static texts mixed
410         # with non-static Elements.
411         # flatten this list, write into the stream.
412         for x in handler.res:
413                 if isinstance(x, Element):
414                         if isinstance(x, StreamingElement):
415                                 finish = False
416                                 x.setStream(stream)
417                         x.render(stream)
418                 else:
419                         stream.write(str(x))
420
421         def ping(s):
422                 from twisted.internet import reactor
423                 s.write("\n");
424                 reactor.callLater(3, ping, s)
425         
426         # if we met a "StreamingElement", there is at least one
427         # element which wants to output data more than once,
428         # i.e. on host-originated changes.
429         # in this case, don't finish yet, don't cleanup yet,
430         # but instead do that when the client disconnects.
431         if finish:
432                 handler.cleanup()
433                 stream.finish()
434         else:
435                 # ok.
436                 # you *need* something which constantly sends something in a regular interval,
437                 # in order to detect disconnected clients.
438                 # i agree that this "ping" sucks terrible, so better be sure to have something 
439                 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
440                 ping(stream)
441                 stream.closed_callback = lambda: handler.cleanup()