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["EPGNOW"] = EPG(session,func=EPG.NOW)
61 self["TimerList"] = Timer(session,func = Timer.LIST)
62 self["TimerAddEventID"] = Timer(session,func = Timer.ADDBYID)
63 self["TimerAdd"] = Timer(session,func = Timer.ADD)
64 self["MovieList"] = Movie(session)
65 self["Volume"] = Volume(session)
67 def browseTo(self, reftobrowse):
68 self["ServiceListBrowse"].root = reftobrowse
70 def zapTo(self, reftozap):
71 self.session.nav.playService(reftozap)
73 # TODO: (really.) put screens into own files.
74 class Streaming(WebScreen):
75 def __init__(self, session):
76 WebScreen.__init__(self, session)
77 from Components.Sources.StreamService import StreamService
78 self["StreamService"] = StreamService(self.session.nav)
80 class StreamingM3U(WebScreen):
81 def __init__(self, session):
82 WebScreen.__init__(self, session)
83 from Components.Sources.StaticText import StaticText
84 from Components.Sources.Config import Config
85 from Components.config import config
87 self["ref"] = StaticText()
88 self["localip"] = Config(config.network.ip)
90 # implements the 'render'-call.
91 # this will act as a downstream_element, like a renderer.
92 class OneTimeElement(Element):
93 def __init__(self, id):
94 Element.__init__(self)
97 # CHECKME: is this ok performance-wise?
98 def handleCommand(self, args):
99 if self.source_id.find(",") >=0:
100 paramlist = self.source_id.split(",")
102 for key in paramlist:
103 arg = args.get(key, [])
107 list[key] = "".join(arg)
110 self.source.handleCommand(list)
112 for c in args.get(self.source_id, []):
113 self.source.handleCommand(c)
115 def render(self, stream):
116 t = self.source.getHTML(self.source_id)
117 if isinstance(t, unicode):
118 t = t.encode("utf-8")
136 class StreamingElement(OneTimeElement):
137 def __init__(self, id):
138 OneTimeElement.__init__(self, id)
141 def changed(self, what):
143 self.render(self.stream)
145 def setStream(self, stream):
148 def filter_none(string):
153 return s.replace("&", "&").replace("<", "<").replace('"', '"').replace(">", ">")
154 except AttributeError:
158 def filter_javascript_escape(s):
159 return s.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"')
162 return s.replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+')
164 # a to-be-filled list item
166 def __init__(self, name, filterfnc):
168 self.filterfnc = filterfnc
170 class TextToHTML(Converter):
171 def __init__(self, arg):
172 Converter.__init__(self, arg)
174 def getHTML(self, id):
175 return self.source.text # encode & etc. here!
177 # a null-output. Useful if you only want to issue a command.
178 class Null(Converter):
179 def __init__(self, arg):
180 Converter.__init__(self, arg)
182 def getHTML(self, id):
185 class JavascriptUpdate(Converter):
186 def __init__(self, arg):
187 Converter.__init__(self, arg)
189 def getHTML(self, id):
190 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
191 # all other will replace this in JS
192 return '<script>parent.set("' + id + '", "' + filter_javascript_escape(self.source.text) + '");</script>\n'
194 # the performant 'listfiller'-engine (plfe)
195 class ListFiller(Converter):
196 def __init__(self, arg):
197 Converter.__init__(self, arg)
201 lut = self.source.lut
203 # now build a ["string", 1, "string", 2]-styled list, with indices into the
204 # list to avoid lookup of item name for each entry
206 for element in self.converter_arguments:
207 if isinstance(element, str):
208 lutlist.append(element)
209 elif isinstance(element, ListItem):
210 lutlist.append((lut[element.name], element.filterfnc))
212 # now, for the huge list, do:
215 for element in lutlist:
216 if isinstance(element, str):
219 res += str(element[1](item[element[0]]))
220 # (this will be done in c++ later!)
223 text = property(getText)
225 class webifHandler(ContentHandler):
226 def __init__(self, session):
230 self.session = session
233 def startElement(self, name, attrs):
234 if name == "e2:screen":
235 self.screen = eval(attrs["name"])(self.session) # fixme
236 self.screens.append(self.screen)
239 if name[:3] == "e2:":
242 tag = "<" + name + ''.join([' ' + key + '="' + val + '"' for (key, val) in attrs.items()]) + ">"
243 tag = tag.encode("UTF-8")
247 elif self.mode == 1: # expect "<e2:element>"
248 assert name == "e2:element", "found %s instead of e2:element" % name
249 source = attrs["source"]
250 self.source_id = str(attrs.get("id", source))
251 self.source = self.screen[source]
252 self.is_streaming = "streaming" in attrs
253 elif self.mode == 2: # expect "<e2:convert>"
254 if name[:3] == "e2:":
255 assert name == "e2:convert"
257 ctype = attrs["type"]
259 # TODO: we need something better here
260 if ctype[:4] == "web:": # for now
261 self.converter = eval(ctype[4:])
264 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
266 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
271 assert name == "e2:item", "found %s instead of e2:item!" % name
272 assert "name" in attrs, "e2:item must have a name= attribute!"
274 filter = {"": filter_none, "javascript_escape": filter_javascript_escape, "xml": filter_xml, "uri": filter_uri}[attrs.get("filter", "")]
276 self.sub.append(ListItem(attrs["name"], filter))
278 def endElement(self, name):
279 if name == "e2:screen":
283 tag = "</" + name + ">"
286 elif self.mode == 2 and name[:3] != "e2:":
288 elif self.mode == 2: # closed 'convert' -> sub
289 self.sub = lreduce(self.sub)
290 if len(self.sub) == 1:
291 self.sub = self.sub[0]
292 c = self.converter(self.sub)
293 c.connect(self.source)
297 elif self.mode == 1: # closed 'element'
298 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
299 if not self.is_streaming:
300 c = OneTimeElement(self.source_id)
302 c = StreamingElement(self.source_id)
304 c.connect(self.source)
306 self.screen.renderer.append(c)
309 if name[:3] == "e2:":
312 def processingInstruction(self, target, data):
313 self.res.append('<?' + target + ' ' + data + '>')
315 def characters(self, ch):
316 ch = ch.encode("UTF-8")
322 def startEntity(self, name):
323 self.res.append('&' + name + ';');
326 for screen in self.screens:
330 print "screen cleanup!"
331 for screen in self.screens:
337 # ouch, can be made better
341 if isinstance(x, str) or isinstance(x, unicode):
342 if isinstance(x, unicode):
343 x = x.encode("UTF-8")
349 if string is not None:
353 if string is not None:
358 def renderPage(stream, path, req, session):
360 # read in the template, create required screens
361 # we don't have persistense yet.
362 # if we had, this first part would only be done once.
363 handler = webifHandler(session)
364 parser = make_parser()
365 parser.setFeature(feature_namespaces, 0)
366 parser.setContentHandler(handler)
367 parser.parse(open(util.sibpath(__file__, path)))
369 # by default, we have non-streaming pages
372 # first, apply "commands" (aka. URL argument)
373 for x in handler.res:
374 if isinstance(x, Element):
375 x.handleCommand(req.args)
379 # now, we have a list with static texts mixed
380 # with non-static Elements.
381 # flatten this list, write into the stream.
382 for x in lreduce(handler.res):
383 if isinstance(x, Element):
384 if isinstance(x, StreamingElement):
392 from twisted.internet import reactor
394 reactor.callLater(3, ping, s)
396 # if we met a "StreamingElement", there is at least one
397 # element which wants to output data more than once,
398 # i.e. on host-originated changes.
399 # in this case, don't finish yet, don't cleanup yet,
400 # but instead do that when the client disconnects.
406 # you *need* something which constantly sends something in a regular interval,
407 # in order to detect disconnected clients.
408 # i agree that this "ping" sucks terrible, so better be sure to have something
409 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
411 stream.closed_callback = lambda: handler.cleanup()