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.RequestData import RequestData
25 from Components.Sources.FrontendStatus import FrontendStatus
27 from Components.Converter.Converter import Converter
29 from Components.Element import Element
31 from xml.sax import make_parser
32 from xml.sax.handler import ContentHandler, feature_namespaces
33 from twisted.python import util
37 # prototype of the new web frontend template system.
39 class WebScreen(Screen):
40 def __init__(self, session,request):
41 Screen.__init__(self, session)
42 self.stand_alone = True
43 self.request = request
45 class TestScreen(InfoBarServiceName, InfoBarEvent,InfoBarTuner, WebScreen):
46 def __init__(self, session,request):
47 WebScreen.__init__(self, session,request)
48 InfoBarServiceName.__init__(self)
49 InfoBarEvent.__init__(self)
50 InfoBarTuner.__init__(self)
51 self["CurrentTime"] = Clock()
52 # self["TVSystem"] = Config(config.av.tvsystem)
53 # self["OSDLanguage"] = Config(config.osd.language)
54 # self["FirstRun"] = Config(config.misc.firstrun)
55 from enigma import eServiceReference
56 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')
57 self["ServiceList"] = ServiceList(fav, command_func = self.zapTo, validate_commands=False)
58 self["ServiceListBrowse"] = ServiceList(fav, command_func = self.browseTo, validate_commands=False)
59 self["Volume"] = Volume(session)
60 self["EPGTITLE"] = EPG(session,func=EPG.TITLE)
61 self["EPGSERVICE"] = EPG(session,func=EPG.SERVICE)
62 self["EPGNOW"] = EPG(session,func=EPG.NOW)
63 self["TimerList"] = Timer(session,func = Timer.LIST)
64 self["TimerAddEventID"] = Timer(session,func = Timer.ADDBYID)
65 self["TimerAdd"] = Timer(session,func = Timer.ADD)
66 self["TimerDel"] = Timer(session,func = Timer.DEL)
67 self["MovieList"] = Movie(session)
68 self["Volume"] = Volume(session)
69 self["Message"] = Message(session)
71 def browseTo(self, reftobrowse):
72 self["ServiceListBrowse"].root = reftobrowse
74 def zapTo(self, reftozap):
75 self.session.nav.playService(reftozap)
77 # TODO: (really.) put screens into own files.
78 class Streaming(WebScreen):
79 def __init__(self, session,request):
80 WebScreen.__init__(self, session,request)
81 from Components.Sources.StreamService import StreamService
82 self["StreamService"] = StreamService(self.session.nav)
84 class StreamingM3U(WebScreen):
85 def __init__(self, session,request):
86 WebScreen.__init__(self, session,request)
87 from Components.Sources.StaticText import StaticText
88 from Components.Sources.Config import Config
89 from Components.config import config
90 self["ref"] = StaticText()
91 self["localip"] = RequestData(request,what=RequestData.HOST)
93 # implements the 'render'-call.
94 # this will act as a downstream_element, like a renderer.
95 class OneTimeElement(Element):
96 def __init__(self, id):
97 Element.__init__(self)
100 # CHECKME: is this ok performance-wise?
101 def handleCommand(self, args):
102 if self.source_id.find(",") >=0:
103 paramlist = self.source_id.split(",")
105 for key in paramlist:
106 arg = args.get(key, [])
110 list[key] = "".join(arg)
113 self.source.handleCommand(list)
115 for c in args.get(self.source_id, []):
116 self.source.handleCommand(c)
118 def render(self, stream):
119 t = self.source.getHTML(self.source_id)
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
191 lutlist = [ isinstance(element, basestring) and (element, None) or (lut[element.name], element.filternum) for element in conv_args ]
193 # now, for the huge list, do:
195 append = strlist.append
197 for (element, filternum) in lutlist:
201 append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
203 append(str(item[element]).replace("&", "&").replace("<", "<").replace('"', '"').replace(">", ">"))
205 append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
207 append(str(item[element]))
208 # (this will be done in c++ later!)
209 return ''.join(strlist)
211 text = property(getText)
213 class webifHandler(ContentHandler):
214 def __init__(self, session,request):
218 self.session = session
220 self.request = request
222 def startElement(self, name, attrs):
223 if name == "e2:screen":
224 self.screen = eval(attrs["name"])(self.session,self.request) # fixme
225 self.screens.append(self.screen)
228 if name[:3] == "e2:":
231 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
235 tag = ''.join(tag)#.encode('utf-8')
239 elif self.mode == 1: # expect "<e2:element>"
240 assert name == "e2:element", "found %s instead of e2:element" % name
241 source = attrs["source"]
242 self.source_id = str(attrs.get("id", source))
243 self.source = self.screen[source]
244 self.is_streaming = "streaming" in attrs
245 elif self.mode == 2: # expect "<e2:convert>"
246 if name[:3] == "e2:":
247 assert name == "e2:convert"
249 ctype = attrs["type"]
251 # TODO: we need something better here
252 if ctype[:4] == "web:": # for now
253 self.converter = eval(ctype[4:])
256 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
258 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
263 assert name == "e2:item", "found %s instead of e2:item!" % name
264 assert "name" in attrs, "e2:item must have a name= attribute!"
265 filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4}[attrs.get("filter", "")]
266 self.sub.append(ListItem(attrs["name"], filter))
268 def endElement(self, name):
269 if name == "e2:screen":
273 tag = "</" + name + ">"
276 elif self.mode == 2 and name[:3] != "e2:":
278 elif self.mode == 2: # closed 'convert' -> sub
279 if len(self.sub) == 1:
280 self.sub = self.sub[0]
281 c = self.converter(self.sub)
282 c.connect(self.source)
285 elif self.mode == 1: # closed 'element'
286 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
287 if not self.is_streaming:
288 c = OneTimeElement(self.source_id)
290 c = StreamingElement(self.source_id)
292 c.connect(self.source)
294 self.screen.renderer.append(c)
297 if name[:3] == "e2:":
300 def processingInstruction(self, target, data):
301 self.res.append('<?' + target + ' ' + data + '>')
303 def characters(self, ch):
304 ch = ch.encode('utf-8')
310 def startEntity(self, name):
311 self.res.append('&' + name + ';');
314 for screen in self.screens:
318 print "screen cleanup!"
319 for screen in self.screens:
324 def renderPage(stream, path, req, session):
326 # read in the template, create required screens
327 # we don't have persistense yet.
328 # if we had, this first part would only be done once.
329 handler = webifHandler(session,req)
330 parser = make_parser()
331 parser.setFeature(feature_namespaces, 0)
332 parser.setContentHandler(handler)
333 parser.parse(open(util.sibpath(__file__, path)))
335 # by default, we have non-streaming pages
338 # first, apply "commands" (aka. URL argument)
339 for x in handler.res:
340 if isinstance(x, Element):
341 x.handleCommand(req.args)
345 # now, we have a list with static texts mixed
346 # with non-static Elements.
347 # flatten this list, write into the stream.
348 for x in handler.res:
349 if isinstance(x, Element):
350 if isinstance(x, StreamingElement):
358 from twisted.internet import reactor
360 reactor.callLater(3, ping, s)
362 # if we met a "StreamingElement", there is at least one
363 # element which wants to output data more than once,
364 # i.e. on host-originated changes.
365 # in this case, don't finish yet, don't cleanup yet,
366 # but instead do that when the client disconnects.
372 # you *need* something which constantly sends something in a regular interval,
373 # in order to detect disconnected clients.
374 # i agree that this "ping" sucks terrible, so better be sure to have something
375 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
377 stream.closed_callback = lambda: handler.cleanup()