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)
135 class StreamingElement(OneTimeElement):
136 def __init__(self, id):
137 OneTimeElement.__init__(self, id)
140 def changed(self, what):
142 self.render(self.stream)
144 def setStream(self, stream):
147 # a to-be-filled list item
149 def __init__(self, name, filternum):
151 self.filternum = filternum
153 class TextToHTML(Converter):
154 def __init__(self, arg):
155 Converter.__init__(self, arg)
157 def getHTML(self, id):
158 return self.source.text # encode & etc. here!
160 # a null-output. Useful if you only want to issue a command.
161 class Null(Converter):
162 def __init__(self, arg):
163 Converter.__init__(self, arg)
165 def getHTML(self, id):
168 class JavascriptUpdate(Converter):
169 def __init__(self, arg):
170 Converter.__init__(self, arg)
172 def getHTML(self, id):
173 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
174 # all other will replace this in JS
175 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
177 # the performant 'listfiller'-engine (plfe)
178 class ListFiller(Converter):
179 def __init__(self, arg):
180 Converter.__init__(self, arg)
184 lut = self.source.lut
185 conv_args = self.converter_arguments
187 # now build a ["string", 1, "string", 2]-styled list, with indices into the
188 # list to avoid lookup of item name for each entry
190 append = lutlist.append
191 for element in conv_args:
192 if isinstance(element, str):
193 append((element, None))
195 append((lut[element.name], element.filternum))
197 # now, for the huge list, do:
199 append = strlist.append
201 for (element, filternum) in lutlist:
205 append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
207 append(str(item[element]).replace("&", "&").replace("<", "<").replace('"', '"').replace(">", ">"))
209 append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
211 append(str(item[element]))
212 res = "".join(strlist)
213 # (this will be done in c++ later!)
216 text = property(getText)
218 class webifHandler(ContentHandler):
219 def __init__(self, session):
223 self.session = session
226 def startElement(self, name, attrs):
227 if name == "e2:screen":
228 self.screen = eval(attrs["name"])(self.session) # fixme
229 self.screens.append(self.screen)
232 if name[:3] == "e2:":
235 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
243 elif self.mode == 1: # expect "<e2:element>"
244 assert name == "e2:element", "found %s instead of e2:element" % name
245 source = attrs["source"]
246 self.source_id = str(attrs.get("id", source))
247 self.source = self.screen[source]
248 self.is_streaming = "streaming" in attrs
249 elif self.mode == 2: # expect "<e2:convert>"
250 if name[:3] == "e2:":
251 assert name == "e2:convert"
253 ctype = attrs["type"]
255 # TODO: we need something better here
256 if ctype[:4] == "web:": # for now
257 self.converter = eval(ctype[4:])
260 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
262 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
267 assert name == "e2:item", "found %s instead of e2:item!" % name
268 assert "name" in attrs, "e2:item must have a name= attribute!"
269 filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4}[attrs.get("filter", "")]
270 self.sub.append(ListItem(attrs["name"], filter))
272 def endElement(self, name):
273 if name == "e2:screen":
277 tag = "</" + name + ">"
280 elif self.mode == 2 and name[:3] != "e2:":
282 elif self.mode == 2: # closed 'convert' -> sub
283 self.sub = lreduce(self.sub)
284 if len(self.sub) == 1:
285 self.sub = self.sub[0]
286 c = self.converter(self.sub)
287 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:
331 # ouch, can be made better
335 if isinstance(x, str):
341 if string is not None:
345 if string is not None:
350 def renderPage(stream, path, req, session):
352 # read in the template, create required screens
353 # we don't have persistense yet.
354 # if we had, this first part would only be done once.
355 handler = webifHandler(session)
356 parser = make_parser()
357 parser.setFeature(feature_namespaces, 0)
358 parser.setContentHandler(handler)
359 parser.parse(open(util.sibpath(__file__, path)))
361 # by default, we have non-streaming pages
364 # first, apply "commands" (aka. URL argument)
365 for x in handler.res:
366 if isinstance(x, Element):
367 x.handleCommand(req.args)
371 # now, we have a list with static texts mixed
372 # with non-static Elements.
373 # flatten this list, write into the stream.
374 for x in lreduce(handler.res):
375 if isinstance(x, Element):
376 if isinstance(x, StreamingElement):
384 from twisted.internet import reactor
386 reactor.callLater(3, ping, s)
388 # if we met a "StreamingElement", there is at least one
389 # element which wants to output data more than once,
390 # i.e. on host-originated changes.
391 # in this case, don't finish yet, don't cleanup yet,
392 # but instead do that when the client disconnects.
398 # you *need* something which constantly sends something in a regular interval,
399 # in order to detect disconnected clients.
400 # i agree that this "ping" sucks terrible, so better be sure to have something
401 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
403 stream.closed_callback = lambda: handler.cleanup()