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 WebComponents.Sources.Message import Message
24 from WebComponents.Sources.PowerState import PowerState
25 from WebComponents.Sources.RemoteControl import RemoteControl
27 from WebComponents.Sources.RequestData import RequestData
28 from Components.Sources.FrontendStatus import FrontendStatus
30 from Components.Converter.Converter import Converter
32 from Components.Element import Element
34 from xml.sax import make_parser
35 from xml.sax.handler import ContentHandler, feature_namespaces
36 from twisted.python import util
40 # prototype of the new web frontend template system.
42 class WebScreen(Screen):
43 def __init__(self, session,request):
44 Screen.__init__(self, session)
45 self.stand_alone = True
46 self.request = request
48 class TestScreen(InfoBarServiceName, InfoBarEvent,InfoBarTuner, WebScreen):
49 def __init__(self, session,request):
50 WebScreen.__init__(self, session,request)
51 InfoBarServiceName.__init__(self)
52 InfoBarEvent.__init__(self)
53 InfoBarTuner.__init__(self)
54 self["CurrentTime"] = Clock()
55 # self["TVSystem"] = Config(config.av.tvsystem)
56 # self["OSDLanguage"] = Config(config.osd.language)
57 # self["FirstRun"] = Config(config.misc.firstrun)
58 from enigma import eServiceReference
59 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')
60 self["ServiceList"] = ServiceList(fav, command_func = self.zapTo, validate_commands=False)
61 self["ServiceListBrowse"] = ServiceList(fav, command_func = self.browseTo, validate_commands=False)
62 self["Volume"] = Volume(session)
63 self["EPGTITLE"] = EPG(session,func=EPG.TITLE)
64 self["EPGSERVICE"] = EPG(session,func=EPG.SERVICE)
65 self["EPGNOW"] = EPG(session,func=EPG.NOW)
66 self["TimerList"] = Timer(session,func = Timer.LIST)
67 self["TimerAddEventID"] = Timer(session,func = Timer.ADDBYID)
68 self["TimerAdd"] = Timer(session,func = Timer.ADD)
69 self["TimerDel"] = Timer(session,func = Timer.DEL)
70 self["MovieList"] = Movie(session)
71 self["Volume"] = Volume(session)
72 self["Message"] = Message(session)
73 self["PowerState"] = PowerState(session)
74 self["RemoteControl"] = RemoteControl(session)
77 def browseTo(self, reftobrowse):
78 self["ServiceListBrowse"].root = reftobrowse
80 def zapTo(self, reftozap):
81 self.session.nav.playService(reftozap)
83 # TODO: (really.) put screens into own files.
84 class Streaming(WebScreen):
85 def __init__(self, session,request):
86 WebScreen.__init__(self, session,request)
87 from Components.Sources.StreamService import StreamService
88 self["StreamService"] = StreamService(self.session.nav)
90 class StreamingM3U(WebScreen):
91 def __init__(self, session,request):
92 WebScreen.__init__(self, session,request)
93 from Components.Sources.StaticText import StaticText
94 from Components.Sources.Config import Config
95 from Components.config import config
96 self["ref"] = StaticText()
97 self["localip"] = RequestData(request,what=RequestData.HOST)
99 # implements the 'render'-call.
100 # this will act as a downstream_element, like a renderer.
101 class OneTimeElement(Element):
102 def __init__(self, id):
103 Element.__init__(self)
106 # CHECKME: is this ok performance-wise?
107 def handleCommand(self, args):
108 if self.source_id.find(",") >=0:
109 paramlist = self.source_id.split(",")
111 for key in paramlist:
112 arg = args.get(key, [])
116 list[key] = "".join(arg)
119 self.source.handleCommand(list)
121 for c in args.get(self.source_id, []):
122 self.source.handleCommand(c)
124 def render(self, stream):
125 t = self.source.getHTML(self.source_id)
143 class StreamingElement(OneTimeElement):
144 def __init__(self, id):
145 OneTimeElement.__init__(self, id)
148 def changed(self, what):
150 self.render(self.stream)
152 def setStream(self, stream):
155 # a to-be-filled list item
157 def __init__(self, name, filternum):
159 self.filternum = filternum
161 class TextToHTML(Converter):
162 def __init__(self, arg):
163 Converter.__init__(self, arg)
165 def getHTML(self, id):
166 return self.source.text # encode & etc. here!
168 # a null-output. Useful if you only want to issue a command.
169 class Null(Converter):
170 def __init__(self, arg):
171 Converter.__init__(self, arg)
173 def getHTML(self, id):
176 class JavascriptUpdate(Converter):
177 def __init__(self, arg):
178 Converter.__init__(self, arg)
180 def getHTML(self, id):
181 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
182 # all other will replace this in JS
183 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
185 # the performant 'listfiller'-engine (plfe)
186 class ListFiller(Converter):
187 def __init__(self, arg):
188 Converter.__init__(self, arg)
192 lut = self.source.lut
193 conv_args = self.converter_arguments
195 # now build a ["string", 1, "string", 2]-styled list, with indices into the
196 # list to avoid lookup of item name for each entry
197 lutlist = [ isinstance(element, basestring) and (element, None) or (lut[element.name], element.filternum) for element in conv_args ]
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 # (this will be done in c++ later!)
215 return ''.join(strlist)
217 text = property(getText)
219 class webifHandler(ContentHandler):
220 def __init__(self, session,request):
224 self.session = session
226 self.request = request
228 def startElement(self, name, attrs):
229 if name == "e2:screen":
230 self.screen = eval(attrs["name"])(self.session,self.request) # 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 if len(self.sub) == 1:
286 self.sub = self.sub[0]
287 c = self.converter(self.sub)
288 c.connect(self.source)
291 elif self.mode == 1: # closed 'element'
292 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
293 if not self.is_streaming:
294 c = OneTimeElement(self.source_id)
296 c = StreamingElement(self.source_id)
298 c.connect(self.source)
300 self.screen.renderer.append(c)
303 if name[:3] == "e2:":
306 def processingInstruction(self, target, data):
307 self.res.append('<?' + target + ' ' + data + '>')
309 def characters(self, ch):
310 ch = ch.encode('utf-8')
316 def startEntity(self, name):
317 self.res.append('&' + name + ';');
320 for screen in self.screens:
324 print "screen cleanup!"
325 for screen in self.screens:
330 def renderPage(stream, path, req, session):
332 # read in the template, create required screens
333 # we don't have persistense yet.
334 # if we had, this first part would only be done once.
335 handler = webifHandler(session,req)
336 parser = make_parser()
337 parser.setFeature(feature_namespaces, 0)
338 parser.setContentHandler(handler)
339 parser.parse(open(util.sibpath(__file__, path)))
341 # by default, we have non-streaming pages
344 # first, apply "commands" (aka. URL argument)
345 for x in handler.res:
346 if isinstance(x, Element):
347 x.handleCommand(req.args)
351 # now, we have a list with static texts mixed
352 # with non-static Elements.
353 # flatten this list, write into the stream.
354 for x in handler.res:
355 if isinstance(x, Element):
356 if isinstance(x, StreamingElement):
364 from twisted.internet import reactor
366 reactor.callLater(3, ping, s)
368 # if we met a "StreamingElement", there is at least one
369 # element which wants to output data more than once,
370 # i.e. on host-originated changes.
371 # in this case, don't finish yet, don't cleanup yet,
372 # but instead do that when the client disconnects.
378 # you *need* something which constantly sends something in a regular interval,
379 # in order to detect disconnected clients.
380 # i agree that this "ping" sucks terrible, so better be sure to have something
381 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
383 stream.closed_callback = lambda: handler.cleanup()