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