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