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