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