2 # OK, this is more than a proof of concept
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
11 from Screens.Screen import Screen
12 from Tools.Import import my_import
15 from Screens.InfoBarGenerics import InfoBarServiceName, InfoBarEvent, InfoBarTuner
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 Components.Sources.FrontendStatus import FrontendStatus
25 from Components.Converter.Converter import Converter
27 from Components.Element import Element
29 from xml.sax import make_parser
30 from xml.sax.handler import ContentHandler, feature_namespaces
31 from twisted.python import util
35 # prototype of the new web frontend template system.
37 class WebScreen(Screen):
38 def __init__(self, session):
39 Screen.__init__(self, session)
40 self.stand_alone = True
43 class TestScreen(InfoBarServiceName, InfoBarEvent,InfoBarTuner, WebScreen):
44 def __init__(self, session):
45 WebScreen.__init__(self, session)
46 InfoBarServiceName.__init__(self)
47 InfoBarEvent.__init__(self)
48 InfoBarTuner.__init__(self)
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, validate_commands=False)
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 self["TimerList"] = Timer(session)
62 self["MovieList"] = Movie(session)
63 self["Volume"] = Volume(session)
65 def browseTo(self, reftobrowse):
66 self["ServiceListBrowse"].root = reftobrowse
68 def zapTo(self, reftozap):
69 self.session.nav.playService(reftozap)
71 # TODO: (really.) put screens into own files.
72 class Streaming(WebScreen):
73 def __init__(self, session):
74 WebScreen.__init__(self, session)
75 from Components.Sources.StreamService import StreamService
76 self["StreamService"] = StreamService(self.session.nav)
78 class StreamingM3U(WebScreen):
79 def __init__(self, session):
80 WebScreen.__init__(self, session)
81 from Components.Sources.StaticText import StaticText
82 from Components.Sources.Config import Config
83 from Components.config import config
85 self["ref"] = StaticText()
86 self["localip"] = Config(config.network.ip)
88 # implements the 'render'-call.
89 # this will act as a downstream_element, like a renderer.
90 class OneTimeElement(Element):
91 def __init__(self, id):
92 Element.__init__(self)
95 # CHECKME: is this ok performance-wise?
96 def handleCommand(self, args):
97 for c in args.get(self.source_id, []):
98 self.source.handleCommand(c)
100 def render(self, stream):
101 t = self.source.getHTML(self.source_id)
102 if isinstance(t, unicode):
103 t = t.encode("utf-8")
121 class StreamingElement(OneTimeElement):
122 def __init__(self, id):
123 OneTimeElement.__init__(self, id)
126 def changed(self, what):
128 self.render(self.stream)
130 def setStream(self, stream):
133 def filter_none(string):
137 return s.replace("&", "&").replace("<", "<").replace('"', '"').replace(">", ">")
139 def filter_javascript_escape(s):
140 return s.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"')
143 return s.replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+')
145 # a to-be-filled list item
147 def __init__(self, name, filterfnc):
149 self.filterfnc = filterfnc
151 class TextToHTML(Converter):
152 def __init__(self, arg):
153 Converter.__init__(self, arg)
155 def getHTML(self, id):
156 return self.source.text # encode & etc. here!
158 # a null-output. Useful if you only want to issue a command.
159 class Null(Converter):
160 def __init__(self, arg):
161 Converter.__init__(self, arg)
163 def getHTML(self, id):
166 class JavascriptUpdate(Converter):
167 def __init__(self, arg):
168 Converter.__init__(self, arg)
170 def getHTML(self, id):
171 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
172 # all other will replace this in JS
173 return '<script>parent.set("' + id + '", "' + filter_javascript_escape(self.source.text) + '");</script>\n'
175 # the performant 'listfiller'-engine (plfe)
176 class ListFiller(Converter):
177 def __init__(self, arg):
178 Converter.__init__(self, arg)
182 lut = self.source.lut
184 # now build a ["string", 1, "string", 2]-styled list, with indices into the
185 # list to avoid lookup of item name for each entry
187 for element in self.converter_arguments:
188 if isinstance(element, str):
189 lutlist.append(element)
190 elif isinstance(element, ListItem):
191 lutlist.append((lut[element.name], element.filterfnc))
193 # now, for the huge list, do:
196 for element in lutlist:
197 if isinstance(element, str):
200 res += str(element[1](item[element[0]]))
201 # (this will be done in c++ later!)
204 text = property(getText)
206 class webifHandler(ContentHandler):
207 def __init__(self, session):
211 self.session = session
214 def startElement(self, name, attrs):
215 if name == "e2:screen":
216 self.screen = eval(attrs["name"])(self.session) # fixme
217 self.screens.append(self.screen)
220 if name[:3] == "e2:":
223 tag = "<" + name + ''.join([' ' + key + '="' + val + '"' for (key, val) in attrs.items()]) + ">"
224 tag = tag.encode("UTF-8")
228 elif self.mode == 1: # expect "<e2:element>"
229 assert name == "e2:element", "found %s instead of e2:element" % name
230 source = attrs["source"]
231 self.source_id = str(attrs.get("id", source))
232 self.source = self.screen[source]
233 self.is_streaming = "streaming" in attrs
234 elif self.mode == 2: # expect "<e2:convert>"
235 if name[:3] == "e2:":
236 assert name == "e2:convert"
238 ctype = attrs["type"]
240 # TODO: we need something better here
241 if ctype[:4] == "web:": # for now
242 self.converter = eval(ctype[4:])
245 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
247 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
252 assert name == "e2:item", "found %s instead of e2:item!" % name
253 assert "name" in attrs, "e2:item must have a name= attribute!"
255 filter = {"": filter_none, "javascript_escape": filter_javascript_escape, "xml": filter_xml, "uri": filter_uri}[attrs.get("filter", "")]
257 self.sub.append(ListItem(attrs["name"], filter))
259 def endElement(self, name):
260 if name == "e2:screen":
264 tag = "</" + name + ">"
267 elif self.mode == 2 and name[:3] != "e2:":
269 elif self.mode == 2: # closed 'convert' -> sub
270 self.sub = lreduce(self.sub)
271 if len(self.sub) == 1:
272 self.sub = self.sub[0]
273 c = self.converter(self.sub)
274 c.connect(self.source)
278 elif self.mode == 1: # closed 'element'
279 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
280 if not self.is_streaming:
281 c = OneTimeElement(self.source_id)
283 c = StreamingElement(self.source_id)
285 c.connect(self.source)
287 self.screen.renderer.append(c)
290 if name[:3] == "e2:":
293 def processingInstruction(self, target, data):
294 self.res.append('<?' + target + ' ' + data + '>')
296 def characters(self, ch):
297 ch = ch.encode("UTF-8")
303 def startEntity(self, name):
304 self.res.append('&' + name + ';');
307 for screen in self.screens:
311 print "screen cleanup!"
312 for screen in self.screens:
318 # ouch, can be made better
322 if isinstance(x, str) or isinstance(x, unicode):
323 if isinstance(x, unicode):
324 x = x.encode("UTF-8")
330 if string is not None:
334 if string is not None:
339 def renderPage(stream, path, req, session):
341 # read in the template, create required screens
342 # we don't have persistense yet.
343 # if we had, this first part would only be done once.
344 handler = webifHandler(session)
345 parser = make_parser()
346 parser.setFeature(feature_namespaces, 0)
347 parser.setContentHandler(handler)
348 parser.parse(open(util.sibpath(__file__, path)))
350 # by default, we have non-streaming pages
353 # first, apply "commands" (aka. URL argument)
354 for x in handler.res:
355 if isinstance(x, Element):
356 x.handleCommand(req.args)
360 # now, we have a list with static texts mixed
361 # with non-static Elements.
362 # flatten this list, write into the stream.
363 for x in lreduce(handler.res):
364 if isinstance(x, Element):
365 if isinstance(x, StreamingElement):
373 from twisted.internet import reactor
375 reactor.callLater(3, ping, s)
377 # if we met a "StreamingElement", there is at least one
378 # element which wants to output data more than once,
379 # i.e. on host-originated changes.
380 # in this case, don't finish yet, don't cleanup yet,
381 # but instead do that when the client disconnects.
387 # you *need* something which constantly sends something in a regular interval,
388 # in order to detect disconnected clients.
389 # i agree that this "ping" sucks terrible, so better be sure to have something
390 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
392 stream.closed_callback = lambda: handler.cleanup()