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