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