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["TimerDel"] = Timer(session,func = Timer.DEL)
65 self["MovieList"] = Movie(session)
66 self["Volume"] = Volume(session)
68 def browseTo(self, reftobrowse):
69 self["ServiceListBrowse"].root = reftobrowse
71 def zapTo(self, reftozap):
72 self.session.nav.playService(reftozap)
74 # TODO: (really.) put screens into own files.
75 class Streaming(WebScreen):
76 def __init__(self, session):
77 WebScreen.__init__(self, session)
78 from Components.Sources.StreamService import StreamService
79 self["StreamService"] = StreamService(self.session.nav)
81 class StreamingM3U(WebScreen):
82 def __init__(self, session):
83 WebScreen.__init__(self, session)
84 from Components.Sources.StaticText import StaticText
85 from Components.Sources.Config import Config
86 from Components.config import config
88 self["ref"] = StaticText()
89 self["localip"] = Config(config.network.ip)
91 # implements the 'render'-call.
92 # this will act as a downstream_element, like a renderer.
93 class OneTimeElement(Element):
94 def __init__(self, id):
95 Element.__init__(self)
98 # CHECKME: is this ok performance-wise?
99 def handleCommand(self, args):
100 if self.source_id.find(",") >=0:
101 paramlist = self.source_id.split(",")
103 for key in paramlist:
104 arg = args.get(key, [])
108 list[key] = "".join(arg)
111 self.source.handleCommand(list)
113 for c in args.get(self.source_id, []):
114 self.source.handleCommand(c)
116 def render(self, stream):
117 t = self.source.getHTML(self.source_id)
118 if isinstance(t, unicode):
119 t = t.encode("utf-8")
137 class StreamingElement(OneTimeElement):
138 def __init__(self, id):
139 OneTimeElement.__init__(self, id)
142 def changed(self, what):
144 self.render(self.stream)
146 def setStream(self, stream):
149 # a to-be-filled list item
151 def __init__(self, name, filternum):
153 self.filternum = filternum
155 class TextToHTML(Converter):
156 def __init__(self, arg):
157 Converter.__init__(self, arg)
159 def getHTML(self, id):
160 return self.source.text # encode & etc. here!
162 # a null-output. Useful if you only want to issue a command.
163 class Null(Converter):
164 def __init__(self, arg):
165 Converter.__init__(self, arg)
167 def getHTML(self, id):
170 class JavascriptUpdate(Converter):
171 def __init__(self, arg):
172 Converter.__init__(self, arg)
174 def getHTML(self, id):
175 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
176 # all other will replace this in JS
177 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
179 # the performant 'listfiller'-engine (plfe)
180 class ListFiller(Converter):
181 def __init__(self, arg):
182 Converter.__init__(self, arg)
186 lut = self.source.lut
187 conv_args = self.converter_arguments
189 # now build a ["string", 1, "string", 2]-styled list, with indices into the
190 # list to avoid lookup of item name for each entry
192 append = lutlist.append
193 for element in conv_args:
194 if isinstance(element, str):
195 append((element, None))
197 append((lut[element.name], element.filternum))
199 # now, for the huge list, do:
201 append = strlist.append
203 for (element, filternum) in lutlist:
207 append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
209 append(str(item[element]).replace("&", "&").replace("<", "<").replace('"', '"').replace(">", ">"))
211 append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
213 append(str(item[element]))
214 res = "".join(strlist)
215 # (this will be done in c++ later!)
218 text = property(getText)
220 class webifHandler(ContentHandler):
221 def __init__(self, session):
225 self.session = session
228 def startElement(self, name, attrs):
229 if name == "e2:screen":
230 self.screen = eval(attrs["name"])(self.session) # fixme
231 self.screens.append(self.screen)
234 if name[:3] == "e2:":
237 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
241 tag = ''.join(tag).encode("UTF-8")
245 elif self.mode == 1: # expect "<e2:element>"
246 assert name == "e2:element", "found %s instead of e2:element" % name
247 source = attrs["source"]
248 self.source_id = str(attrs.get("id", source))
249 self.source = self.screen[source]
250 self.is_streaming = "streaming" in attrs
251 elif self.mode == 2: # expect "<e2:convert>"
252 if name[:3] == "e2:":
253 assert name == "e2:convert"
255 ctype = attrs["type"]
257 # TODO: we need something better here
258 if ctype[:4] == "web:": # for now
259 self.converter = eval(ctype[4:])
262 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
264 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
269 assert name == "e2:item", "found %s instead of e2:item!" % name
270 assert "name" in attrs, "e2:item must have a name= attribute!"
271 filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4}[attrs.get("filter", "")]
272 self.sub.append(ListItem(attrs["name"], filter))
274 def endElement(self, name):
275 if name == "e2:screen":
279 tag = "</" + name + ">"
282 elif self.mode == 2 and name[:3] != "e2:":
284 elif self.mode == 2: # closed 'convert' -> sub
285 self.sub = lreduce(self.sub)
286 if len(self.sub) == 1:
287 self.sub = self.sub[0]
288 c = self.converter(self.sub)
289 c.connect(self.source)
293 elif self.mode == 1: # closed 'element'
294 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
295 if not self.is_streaming:
296 c = OneTimeElement(self.source_id)
298 c = StreamingElement(self.source_id)
300 c.connect(self.source)
302 self.screen.renderer.append(c)
305 if name[:3] == "e2:":
308 def processingInstruction(self, target, data):
309 self.res.append('<?' + target + ' ' + data + '>')
311 def characters(self, ch):
312 ch = ch.encode("UTF-8")
318 def startEntity(self, name):
319 self.res.append('&' + name + ';');
322 for screen in self.screens:
326 print "screen cleanup!"
327 for screen in self.screens:
333 # ouch, can be made better
337 if isinstance(x, str) or isinstance(x, unicode):
338 if isinstance(x, unicode):
339 x = x.encode("UTF-8")
345 if string is not None:
349 if string is not None:
354 def renderPage(stream, path, req, session):
356 # read in the template, create required screens
357 # we don't have persistense yet.
358 # if we had, this first part would only be done once.
359 handler = webifHandler(session)
360 parser = make_parser()
361 parser.setFeature(feature_namespaces, 0)
362 parser.setContentHandler(handler)
363 parser.parse(open(util.sibpath(__file__, path)))
365 # by default, we have non-streaming pages
368 # first, apply "commands" (aka. URL argument)
369 for x in handler.res:
370 if isinstance(x, Element):
371 x.handleCommand(req.args)
375 # now, we have a list with static texts mixed
376 # with non-static Elements.
377 # flatten this list, write into the stream.
378 for x in lreduce(handler.res):
379 if isinstance(x, Element):
380 if isinstance(x, StreamingElement):
388 from twisted.internet import reactor
390 reactor.callLater(3, ping, s)
392 # if we met a "StreamingElement", there is at least one
393 # element which wants to output data more than once,
394 # i.e. on host-originated changes.
395 # in this case, don't finish yet, don't cleanup yet,
396 # but instead do that when the client disconnects.
402 # you *need* something which constantly sends something in a regular interval,
403 # in order to detect disconnected clients.
404 # i agree that this "ping" sucks terrible, so better be sure to have something
405 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
407 stream.closed_callback = lambda: handler.cleanup()