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["TVBrowser"] = Timer(session,func = Timer.TVBROWSER)
71 self["MovieList"] = Movie(session)
72 self["Volume"] = Volume(session)
73 self["Message"] = Message(session)
74 self["PowerState"] = PowerState(session)
75 self["RemoteControl"] = RemoteControl(session)
78 def browseTo(self, reftobrowse):
79 self["ServiceListBrowse"].root = reftobrowse
81 def zapTo(self, reftozap):
82 self.session.nav.playService(reftozap)
84 # TODO: (really.) put screens into own files.
85 class Streaming(WebScreen):
86 def __init__(self, session,request):
87 WebScreen.__init__(self, session,request)
88 from Components.Sources.StreamService import StreamService
89 self["StreamService"] = StreamService(self.session.nav)
91 class StreamingM3U(WebScreen):
92 def __init__(self, session,request):
93 WebScreen.__init__(self, session,request)
94 from Components.Sources.StaticText import StaticText
95 from Components.Sources.Config import Config
96 from Components.config import config
97 self["ref"] = StaticText()
98 self["localip"] = RequestData(request,what=RequestData.HOST)
100 # implements the 'render'-call.
101 # this will act as a downstream_element, like a renderer.
102 class OneTimeElement(Element):
103 def __init__(self, id):
104 Element.__init__(self)
107 # CHECKME: is this ok performance-wise?
108 def handleCommand(self, args):
109 if self.source_id.find(",") >=0:
110 paramlist = self.source_id.split(",")
112 for key in paramlist:
113 arg = args.get(key, [])
117 list[key] = "".join(arg)
120 self.source.handleCommand(list)
122 for c in args.get(self.source_id, []):
123 self.source.handleCommand(c)
125 def render(self, stream):
126 t = self.source.getHTML(self.source_id)
144 class StreamingElement(OneTimeElement):
145 def __init__(self, id):
146 OneTimeElement.__init__(self, id)
149 def changed(self, what):
151 self.render(self.stream)
153 def setStream(self, stream):
156 # a to-be-filled list item
158 def __init__(self, name, filternum):
160 self.filternum = filternum
162 class TextToHTML(Converter):
163 def __init__(self, arg):
164 Converter.__init__(self, arg)
166 def getHTML(self, id):
167 return self.source.text # encode & etc. here!
169 # a null-output. Useful if you only want to issue a command.
170 class Null(Converter):
171 def __init__(self, arg):
172 Converter.__init__(self, arg)
174 def getHTML(self, id):
177 class JavascriptUpdate(Converter):
178 def __init__(self, arg):
179 Converter.__init__(self, arg)
181 def getHTML(self, id):
182 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
183 # all other will replace this in JS
184 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
186 # the performant 'listfiller'-engine (plfe)
187 class ListFiller(Converter):
188 def __init__(self, arg):
189 Converter.__init__(self, arg)
193 lut = self.source.lut
194 conv_args = self.converter_arguments
196 # now build a ["string", 1, "string", 2]-styled list, with indices into the
197 # list to avoid lookup of item name for each entry
198 lutlist = [ isinstance(element, basestring) and (element, None) or (lut[element.name], element.filternum) for element in conv_args ]
200 # now, for the huge list, do:
202 append = strlist.append
204 for (element, filternum) in lutlist:
208 append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
210 append(str(item[element]).replace("&", "&").replace("<", "<").replace('"', '"').replace(">", ">"))
212 append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
214 append(str(item[element]))
215 # (this will be done in c++ later!)
216 return ''.join(strlist)
218 text = property(getText)
220 class webifHandler(ContentHandler):
221 def __init__(self, session,request):
225 self.session = session
227 self.request = request
229 def startElement(self, name, attrs):
230 if name == "e2:screen":
231 self.screen = eval(attrs["name"])(self.session,self.request) # fixme
232 self.screens.append(self.screen)
235 if name[:3] == "e2:":
238 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
242 tag = ''.join(tag)#.encode('utf-8')
246 elif self.mode == 1: # expect "<e2:element>"
247 assert name == "e2:element", "found %s instead of e2:element" % name
248 source = attrs["source"]
249 self.source_id = str(attrs.get("id", source))
250 self.source = self.screen[source]
251 self.is_streaming = "streaming" in attrs
252 elif self.mode == 2: # expect "<e2:convert>"
253 if name[:3] == "e2:":
254 assert name == "e2:convert"
256 ctype = attrs["type"]
258 # TODO: we need something better here
259 if ctype[:4] == "web:": # for now
260 self.converter = eval(ctype[4:])
263 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
265 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
270 assert name == "e2:item", "found %s instead of e2:item!" % name
271 assert "name" in attrs, "e2:item must have a name= attribute!"
272 filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4}[attrs.get("filter", "")]
273 self.sub.append(ListItem(attrs["name"], filter))
275 def endElement(self, name):
276 if name == "e2:screen":
280 tag = "</" + name + ">"
283 elif self.mode == 2 and name[:3] != "e2:":
285 elif self.mode == 2: # closed 'convert' -> sub
286 if len(self.sub) == 1:
287 self.sub = self.sub[0]
288 c = self.converter(self.sub)
289 c.connect(self.source)
292 elif self.mode == 1: # closed 'element'
293 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
294 if not self.is_streaming:
295 c = OneTimeElement(self.source_id)
297 c = StreamingElement(self.source_id)
299 c.connect(self.source)
301 self.screen.renderer.append(c)
304 if name[:3] == "e2:":
307 def processingInstruction(self, target, data):
308 self.res.append('<?' + target + ' ' + data + '>')
310 def characters(self, ch):
311 ch = ch.encode('utf-8')
317 def startEntity(self, name):
318 self.res.append('&' + name + ';');
321 for screen in self.screens:
325 print "screen cleanup!"
326 for screen in self.screens:
331 def renderPage(stream, path, req, session):
333 # read in the template, create required screens
334 # we don't have persistense yet.
335 # if we had, this first part would only be done once.
336 handler = webifHandler(session,req)
337 parser = make_parser()
338 parser.setFeature(feature_namespaces, 0)
339 parser.setContentHandler(handler)
340 parser.parse(open(util.sibpath(__file__, path)))
342 # by default, we have non-streaming pages
345 # first, apply "commands" (aka. URL argument)
346 for x in handler.res:
347 if isinstance(x, Element):
348 x.handleCommand(req.args)
352 # now, we have a list with static texts mixed
353 # with non-static Elements.
354 # flatten this list, write into the stream.
355 for x in handler.res:
356 if isinstance(x, Element):
357 if isinstance(x, StreamingElement):
365 from twisted.internet import reactor
367 reactor.callLater(3, ping, s)
369 # if we met a "StreamingElement", there is at least one
370 # element which wants to output data more than once,
371 # i.e. on host-originated changes.
372 # in this case, don't finish yet, don't cleanup yet,
373 # but instead do that when the client disconnects.
379 # you *need* something which constantly sends something in a regular interval,
380 # in order to detect disconnected clients.
381 # i agree that this "ping" sucks terrible, so better be sure to have something
382 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
384 stream.closed_callback = lambda: handler.cleanup()