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