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