major changes... this hopefully fixes
[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
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 class DummyWebScreen(WebScreen):
61         #use it, if you dont need any source, just to can do a static file with an xml-file
62         def __init__(self, session,request):
63                 WebScreen.__init__(self, session,request)
64
65 class UpdateWebScreen(InfoBarServiceName, InfoBarEvent,InfoBarTuner,WebScreen):
66         def __init__(self, session,request):
67                 WebScreen.__init__(self, session,request)
68                 InfoBarServiceName.__init__(self)
69                 InfoBarEvent.__init__(self)
70                 InfoBarTuner.__init__(self)
71                 self["CurrentTime"] = Clock()
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                 #CurrentService
74                 #Event_Now
75                 #Event_Next
76                 #FrontendStatus
77                 
78 class MessageWebScreen(WebScreen):
79         def __init__(self, session,request):
80                 WebScreen.__init__(self, session,request)
81                 self["Message"] = Message(session)
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                 
150 class TimerWebScreen(WebScreen):
151         def __init__(self, session,request):
152                 WebScreen.__init__(self, session,request)
153                 self["TimerList"] = Timer(session,func = Timer.LIST)
154                 self["TimerAddEventID"] = Timer(session,func = Timer.ADDBYID)
155                 self["TimerAdd"] = Timer(session,func = Timer.ADD)
156                 self["TimerDel"] = Timer(session,func = Timer.DEL)
157                 self["TimerChange"] = Timer(session,func = Timer.CHANGE)
158                 self["TimerListWrite"] = Timer(session,func = Timer.WRITE)
159                 self["TVBrowser"] = Timer(session,func = Timer.TVBROWSER)
160                 self["RecordNow"] = Timer(session,func = Timer.RECNOW)
161
162 class RemoteWebScreen(WebScreen):
163         def __init__(self, session,request):
164                 WebScreen.__init__(self, session,request)
165                 self["RemoteControl"] = RemoteControl(session)
166
167 class PowerWebScreen(WebScreen):
168         def __init__(self, session,request):
169                 WebScreen.__init__(self, session,request)
170                 self["PowerState"] = PowerState(session)
171
172 class ParentControlWebScreen(WebScreen):
173         def __init__(self, session,request):
174                 WebScreen.__init__(self, session,request)
175                 self["ParentControlList"] = ParentControl(session)
176                                 
177 class WAPWebScreen(WebScreen):
178         def __init__(self, session,request):
179                 WebScreen.__init__(self, session,request)
180                 self["WAPFillOptionListSyear"] = WAPfunctions(session,func = WAPfunctions.FILLOPTIONLIST)
181                 self["WAPFillOptionListSday"] = WAPfunctions(session,func = WAPfunctions.FILLOPTIONLIST)
182                 self["WAPFillOptionListSmonth"] = WAPfunctions(session,func = WAPfunctions.FILLOPTIONLIST)
183                 self["WAPFillOptionListShour"] = WAPfunctions(session,func = WAPfunctions.FILLOPTIONLIST)
184                 self["WAPFillOptionListSmin"] = WAPfunctions(session,func = WAPfunctions.FILLOPTIONLIST)
185                 
186                 self["WAPFillOptionListEyear"] = WAPfunctions(session,func = WAPfunctions.FILLOPTIONLIST)
187                 self["WAPFillOptionListEday"] = WAPfunctions(session,func = WAPfunctions.FILLOPTIONLIST)
188                 self["WAPFillOptionListEmonth"] = WAPfunctions(session,func = WAPfunctions.FILLOPTIONLIST)
189                 self["WAPFillOptionListEhour"] = WAPfunctions(session,func = WAPfunctions.FILLOPTIONLIST)
190                 self["WAPFillOptionListEmin"] = WAPfunctions(session,func = WAPfunctions.FILLOPTIONLIST)
191
192                 self["WAPFillOptionListRepeated"] = WAPfunctions(session,func = WAPfunctions.REPEATED)
193                 self["WAPServiceList"] = WAPfunctions(session, func = WAPfunctions.SERVICELIST)
194         
195 class StreamingWebScreen(WebScreen):
196         def __init__(self, session,request):
197                 WebScreen.__init__(self, session,request)
198                 from Components.Sources.StreamService import StreamService
199                 self["StreamService"] = StreamService(self.session.nav)
200
201 class M3UStreamingWebScreen(WebScreen):
202         def __init__(self, session,request):
203                 WebScreen.__init__(self, session,request)
204                 from Components.Sources.StaticText import StaticText
205                 from Components.Sources.Config import Config
206                 from Components.config import config
207                 self["ref"] = StaticText()
208                 self["localip"] = RequestData(request,what=RequestData.HOST)
209
210 class TsM3U(WebScreen):
211         def __init__(self, session,request):
212                 WebScreen.__init__(self, session,request)
213                 from Components.Sources.StaticText import StaticText
214                 from Components.Sources.Config import Config
215                 from Components.config import config
216                 self["file"] = StaticText()
217                 self["localip"] = RequestData(request,what=RequestData.HOST)
218
219 class RestartWebScreen(WebScreen):
220         def __init__(self, session,request):
221                 WebScreen.__init__(self, session,request)
222                 import plugin
223                 plugin.restartWebserver()
224                 
225 class GetPid(WebScreen):
226       def __init__(self, session,request):
227          WebScreen.__init__(self, session,request)
228          from Components.Sources.StaticText import StaticText
229          from enigma import iServiceInformation
230          pids = self.session.nav.getCurrentService()
231          if pids is not None:
232                  pidinfo = pids.info()
233                  VPID = hex(pidinfo.getInfo(iServiceInformation.sVideoPID))
234                  APID = hex(pidinfo.getInfo(iServiceInformation.sAudioPID))
235                  PPID = hex(pidinfo.getInfo(iServiceInformation.sPMTPID))
236          self["pids"] = StaticText("%s,%s,%s"%(PPID.lstrip("0x"),VPID.lstrip("0x"),APID.lstrip("0x")))
237          self["localip"] = RequestData(request,what=RequestData.HOST)
238
239
240 # implements the 'render'-call.
241 # this will act as a downstream_element, like a renderer.
242 class OneTimeElement(Element):
243         def __init__(self, id):
244                 Element.__init__(self)
245                 self.source_id = id
246
247         # CHECKME: is this ok performance-wise?
248         def handleCommand(self, args):
249                 if self.source_id.find(",") >=0:
250                         paramlist = self.source_id.split(",")
251                         list={}
252                         for key in paramlist:
253                                 arg = args.get(key, [])
254                                 if len(arg) == 0:
255                                         list[key] = None        
256                                 elif len(arg) == 1:
257                                         list[key] = "".join(arg)        
258                                 elif len(arg) == 2:
259                                         list[key] = arg[0]
260                         self.source.handleCommand(list)
261                 else:
262                         for c in args.get(self.source_id, []):
263                                 self.source.handleCommand(c)
264                 
265         def render(self, stream):
266                 t = self.source.getHTML(self.source_id)
267                 stream.write(t)
268
269         def execBegin(self):
270                 pass
271         
272         def execEnd(self):
273                 pass
274         
275         def onShow(self):
276                 pass
277
278         def onHide(self):
279                 pass
280         
281         def destroy(self):
282                 pass
283
284 class StreamingElement(OneTimeElement):
285         def __init__(self, id):
286                 OneTimeElement.__init__(self, id)
287                 self.stream = None
288
289         def changed(self, what):
290                 if self.stream:
291                         self.render(self.stream)
292
293         def setStream(self, stream):
294                 self.stream = stream
295
296 # a to-be-filled list item
297 class ListItem:
298         def __init__(self, name, filternum):
299                 self.name = name
300                 self.filternum = filternum
301         
302 class TextToHTML(Converter):
303         def __init__(self, arg):
304                 Converter.__init__(self, arg)
305
306         def getHTML(self, id):
307                 return self.source.text # encode & etc. here!
308
309 class TextToURL(Converter):
310         def __init__(self, arg):
311                 Converter.__init__(self, arg)
312
313         def getHTML(self, id):
314                 return self.source.text.replace(" ","%20")
315
316 class ReturnEmptyXML(Converter):
317         def __init__(self, arg):
318                 Converter.__init__(self, arg)
319                 
320         def getHTML(self, id):
321                 return "<rootElement></rootElement>"
322
323 # a null-output. Useful if you only want to issue a command.
324 class Null(Converter):
325         def __init__(self, arg):
326                 Converter.__init__(self, arg)
327
328         def getHTML(self, id):
329                 return ""
330
331 class JavascriptUpdate(Converter):
332         def __init__(self, arg):
333                 Converter.__init__(self, arg)
334
335         def getHTML(self, id):
336                 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
337                 #                all other will replace this in JS
338                 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '&deg;'))
339
340 # the performant 'listfiller'-engine (plfe)
341 class ListFiller(Converter):
342         def __init__(self, arg):
343                 Converter.__init__(self, arg)
344 #               print "ListFiller-arg: ",arg
345
346         def getText(self):
347                 l = self.source.list
348                 lut = self.source.lut
349                 conv_args = self.converter_arguments
350
351                 # now build a ["string", 1, "string", 2]-styled list, with indices into the
352                 # list to avoid lookup of item name for each entry
353                 lutlist = [ isinstance(element, basestring) and (element, None) or (lut[element.name], element.filternum) for element in conv_args ]
354
355                 # now, for the huge list, do:
356                 strlist = [ ]
357                 append = strlist.append
358                 for item in l:
359                         for (element, filternum) in lutlist:
360                                 if not filternum:
361                                         append(element)
362                                 elif filternum == 2:
363                                         append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
364                                 elif filternum == 3:
365                                         append(str(item[element]).replace("&", "&amp;").replace("<", "&lt;").replace('"', '&quot;').replace(">", "&gt;"))
366                                 elif filternum == 4:
367                                         append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
368                                 else:
369                                         append(str(item[element]))
370                 # (this will be done in c++ later!)
371                 return ''.join(strlist)
372
373         text = property(getText)
374
375 class webifHandler(ContentHandler):
376         def __init__(self, session,request):
377                 self.res = [ ]
378                 self.mode = 0
379                 self.screen = None
380                 self.session = session
381                 self.screens = [ ]
382                 self.request = request
383         
384         def startElement(self, name, attrs):
385                 if name == "e2:screen":
386                         self.screen = eval(attrs["name"])(self.session,self.request) # fixme
387                         self.screens.append(self.screen)
388                         return
389         
390                 if name[:3] == "e2:":
391                         self.mode += 1
392
393                 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
394                 tag.insert(0, name)
395                 tag.insert(0, '<')
396                 tag.append('>')
397                 tag = ''.join(tag)#.encode('utf-8')
398
399                 if self.mode == 0:
400                         self.res.append(tag)
401                 elif self.mode == 1: # expect "<e2:element>"
402                         assert name == "e2:element", "found %s instead of e2:element" % name
403                         source = attrs["source"]
404                         self.source_id = str(attrs.get("id", source))
405                         self.source = self.screen[source]
406                         self.is_streaming = "streaming" in attrs
407                 elif self.mode == 2: # expect "<e2:convert>"
408                         if name[:3] == "e2:":
409                                 assert name == "e2:convert"
410                                 
411                                 ctype = attrs["type"]
412                                 
413                                         # TODO: we need something better here
414                                 if ctype[:4] == "web:": # for now
415                                         self.converter = eval(ctype[4:])
416                                 else:
417                                         try:
418                                                 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
419                                         except ImportError:
420                                                 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
421                                 self.sub = [ ]
422                         else:
423                                 self.sub.append(tag)
424                 elif self.mode == 3:
425                         assert name == "e2:item", "found %s instead of e2:item!" % name
426                         assert "name" in attrs, "e2:item must have a name= attribute!"
427                         filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4}[attrs.get("filter", "")]
428                         self.sub.append(ListItem(attrs["name"], filter))
429
430         def endElement(self, name):
431                 if name == "e2:screen":
432                         self.screen = None
433                         return
434
435                 tag = "</" + name + ">"
436                 if self.mode == 0:
437                         self.res.append(tag)
438                 elif self.mode == 2 and name[:3] != "e2:":
439                         self.sub.append(tag)
440                 elif self.mode == 2: # closed 'convert' -> sub
441                         if len(self.sub) == 1:
442                                 self.sub = self.sub[0]
443                         c = self.converter(self.sub)
444                         c.connect(self.source)
445                         self.source = c
446                         del self.sub
447                 elif self.mode == 1: # closed 'element'
448                         # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
449                         if not self.is_streaming:
450                                 c = OneTimeElement(self.source_id)
451                         else:
452                                 c = StreamingElement(self.source_id)
453                         
454                         c.connect(self.source)
455                         self.res.append(c)
456                         self.screen.renderer.append(c)
457                         del self.source
458
459                 if name[:3] == "e2:":
460                         self.mode -= 1
461
462         def processingInstruction(self, target, data):
463                 self.res.append('<?' + target + ' ' + data + '>')
464         
465         def characters(self, ch):
466                 ch = ch.encode('utf-8')
467                 if self.mode == 0:
468                         self.res.append(ch)
469                 elif self.mode == 2:
470                         self.sub.append(ch)
471         
472         def startEntity(self, name):
473                 self.res.append('&' + name + ';');
474
475         def execBegin(self):
476                 for screen in self.screens:
477                         screen.execBegin()
478
479         def cleanup(self):
480                 print "screen cleanup!"
481                 for screen in self.screens:
482                         screen.execEnd()
483                         screen.doClose()
484                 self.screens = [ ]
485
486 def renderPage(stream, path, req, session):
487         
488         # read in the template, create required screens
489         # we don't have persistense yet.
490         # if we had, this first part would only be done once.
491         handler = webifHandler(session,req)
492         parser = make_parser()
493         parser.setFeature(feature_namespaces, 0)
494         parser.setContentHandler(handler)
495         parser.parse(open(util.sibpath(__file__, path)))
496         
497         # by default, we have non-streaming pages
498         finish = True
499         
500         # first, apply "commands" (aka. URL argument)
501         for x in handler.res:
502                 if isinstance(x, Element):
503                         x.handleCommand(req.args)
504
505         handler.execBegin()
506
507         # now, we have a list with static texts mixed
508         # with non-static Elements.
509         # flatten this list, write into the stream.
510         for x in handler.res:
511                 if isinstance(x, Element):
512                         if isinstance(x, StreamingElement):
513                                 finish = False
514                                 x.setStream(stream)
515                         x.render(stream)
516                 else:
517                         stream.write(str(x))
518
519         def ping(s):
520                 from twisted.internet import reactor
521                 s.write("\n");
522                 reactor.callLater(3, ping, s)
523         
524         # if we met a "StreamingElement", there is at least one
525         # element which wants to output data more than once,
526         # i.e. on host-originated changes.
527         # in this case, don't finish yet, don't cleanup yet,
528         # but instead do that when the client disconnects.
529         if finish:
530                 handler.cleanup()
531                 stream.finish()
532         else:
533                 # ok.
534                 # you *need* something which constantly sends something in a regular interval,
535                 # in order to detect disconnected clients.
536                 # i agree that this "ping" sucks terrible, so better be sure to have something 
537                 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
538                 ping(stream)
539                 stream.closed_callback = lambda: handler.cleanup()