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
20 from WebComponents.Sources.Volume import Volume
21 from WebComponents.Sources.EPG import EPG
22 from WebComponents.Sources.Timer import Timer
23 from WebComponents.Sources.Movie import Movie
24 from WebComponents.Sources.Message import Message
25 from WebComponents.Sources.PowerState import PowerState
26 from WebComponents.Sources.RemoteControl import RemoteControl
27 from WebComponents.Sources.Settings import Settings
28 from WebComponents.Sources.SubServices import SubServices
29 from WebComponents.Sources.ParentControl import ParentControl
30 from WebComponents.Sources.About import About
31 from WebComponents.Sources.RequestData import RequestData
33 from Components.Sources.FrontendStatus import FrontendStatus
35 from Components.Converter.Converter import Converter
37 from Components.Element import Element
39 from xml.sax import make_parser
40 from xml.sax.handler import ContentHandler, feature_namespaces
42 from twisted.python import util
47 # prototype of the new web frontend template system.
49 class WebScreen(Screen):
50 def __init__(self, session, request):
51 Screen.__init__(self, session)
52 self.stand_alone = True
53 self.request = request
55 class TestScreen(InfoBarServiceName, InfoBarEvent,InfoBarTuner, WebScreen):
56 def __init__(self, session,request):
57 WebScreen.__init__(self, session,request)
58 InfoBarServiceName.__init__(self)
59 InfoBarEvent.__init__(self)
60 InfoBarTuner.__init__(self)
61 self["CurrentTime"] = Clock()
62 # self["TVSystem"] = Config(config.av.tvsystem)
63 # self["OSDLanguage"] = Config(config.osd.language)
64 # self["FirstRun"] = Config(config.misc.firstrun)
65 from enigma import eServiceReference
66 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')
67 self["ServiceList"] = ServiceList(fav, command_func = self.zapTo, validate_commands=False)
68 self["ServiceListBrowse"] = ServiceList(fav, command_func = self.browseTo, validate_commands=False)
70 self["ParentControlList"] = ParentControl(session)
71 self["SubServices"] = SubServices(session)
72 self["Volume"] = Volume(session)
73 self["EPGTITLE"] = EPG(session,func=EPG.TITLE)
74 self["EPGSERVICE"] = EPG(session,func=EPG.SERVICE)
75 self["EPGNOW"] = EPG(session,func=EPG.NOW)
76 self["TimerList"] = Timer(session,func = Timer.LIST)
77 self["TimerAddEventID"] = Timer(session,func = Timer.ADDBYID)
78 self["TimerAdd"] = Timer(session,func = Timer.ADD)
79 self["TimerDel"] = Timer(session,func = Timer.DEL)
80 self["TimerChange"] = Timer(session,func = Timer.CHANGE)
81 self["TimerListWrite"] = Timer(session,func = Timer.WRITE)
82 self["TVBrowser"] = Timer(session,func = Timer.TVBROWSER)
83 self["RecordNow"] = Timer(session,func = Timer.RECNOW)
84 self["MovieList"] = Movie(session,func = Movie.LIST)
85 self["MovieFileDel"] = Movie(session,func = Movie.DEL)
86 self["Volume"] = Volume(session)
87 self["Message"] = Message(session)
88 self["PowerState"] = PowerState(session)
89 self["RemoteControl"] = RemoteControl(session)
90 self["Settings"] = Settings(session)
92 self["About"] = About(session)
94 def browseTo(self, reftobrowse):
95 self["ServiceListBrowse"].root = reftobrowse
97 def zapTo(self, reftozap):
98 from Components.config import config
99 pc = config.ParentalControl.configured.value
101 config.ParentalControl.configured.value = False
102 self.session.nav.playService(reftozap)
104 config.ParentalControl.configured.value = pc
106 switsching config.ParentalControl.configured.value
107 ugly, but necessary :(
110 # TODO: (really.) put screens into own files.
111 class Streaming(WebScreen):
112 def __init__(self, session,request):
113 WebScreen.__init__(self, session,request)
114 from Components.Sources.StreamService import StreamService
115 self["StreamService"] = StreamService(self.session.nav)
117 class StreamingM3U(WebScreen):
118 def __init__(self, session,request):
119 WebScreen.__init__(self, session,request)
120 from Components.Sources.StaticText import StaticText
121 from Components.Sources.Config import Config
122 from Components.config import config
123 self["ref"] = StaticText()
124 self["localip"] = RequestData(request,what=RequestData.HOST)
126 class TsM3U(WebScreen):
127 def __init__(self, session,request):
128 WebScreen.__init__(self, session,request)
129 from Components.Sources.StaticText import StaticText
130 from Components.Sources.Config import Config
131 from Components.config import config
132 self["file"] = StaticText()
133 self["localip"] = RequestData(request,what=RequestData.HOST)
135 class RestartTwisted(WebScreen):
136 def __init__(self, session,request):
137 WebScreen.__init__(self, session,request)
139 plugin.restartWebserver()
141 class GetPid(WebScreen):
142 def __init__(self, session,request):
143 WebScreen.__init__(self, session,request)
144 from Components.Sources.StaticText import StaticText
145 from enigma import iServiceInformation
146 pids = self.session.nav.getCurrentService()
148 pidinfo = pids.info()
149 VPID = hex(pidinfo.getInfo(iServiceInformation.sVideoPID))
150 APID = hex(pidinfo.getInfo(iServiceInformation.sAudioPID))
151 PPID = hex(pidinfo.getInfo(iServiceInformation.sPMTPID))
152 self["pids"] = StaticText("%s,%s,%s"%(PPID.lstrip("0x"),VPID.lstrip("0x"),APID.lstrip("0x")))
153 self["localip"] = RequestData(request,what=RequestData.HOST)
156 # implements the 'render'-call.
157 # this will act as a downstream_element, like a renderer.
158 class OneTimeElement(Element):
159 def __init__(self, id):
160 Element.__init__(self)
163 # CHECKME: is this ok performance-wise?
164 def handleCommand(self, args):
165 if self.source_id.find(",") >=0:
166 paramlist = self.source_id.split(",")
168 for key in paramlist:
169 arg = args.get(key, [])
173 list[key] = "".join(arg)
176 self.source.handleCommand(list)
178 for c in args.get(self.source_id, []):
179 self.source.handleCommand(c)
181 def render(self, stream):
182 t = self.source.getHTML(self.source_id)
200 class StreamingElement(OneTimeElement):
201 def __init__(self, id):
202 OneTimeElement.__init__(self, id)
205 def changed(self, what):
207 self.render(self.stream)
209 def setStream(self, stream):
212 # a to-be-filled list item
214 def __init__(self, name, filternum):
216 self.filternum = filternum
218 class TextToHTML(Converter):
219 def __init__(self, arg):
220 Converter.__init__(self, arg)
222 def getHTML(self, id):
223 return self.source.text # encode & etc. here!
225 class ReturnEmptyXML(Converter):
226 def __init__(self, arg):
227 Converter.__init__(self, arg)
229 def getHTML(self, id):
230 return "<rootElement></rootElement>"
232 # a null-output. Useful if you only want to issue a command.
233 class Null(Converter):
234 def __init__(self, arg):
235 Converter.__init__(self, arg)
237 def getHTML(self, id):
240 class JavascriptUpdate(Converter):
241 def __init__(self, arg):
242 Converter.__init__(self, arg)
244 def getHTML(self, id):
245 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
246 # all other will replace this in JS
247 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '°'))
249 # the performant 'listfiller'-engine (plfe)
250 class ListFiller(Converter):
251 def __init__(self, arg):
252 Converter.__init__(self, arg)
256 lut = self.source.lut
257 conv_args = self.converter_arguments
259 # now build a ["string", 1, "string", 2]-styled list, with indices into the
260 # list to avoid lookup of item name for each entry
261 lutlist = [ isinstance(element, basestring) and (element, None) or (lut[element.name], element.filternum) for element in conv_args ]
263 # now, for the huge list, do:
265 append = strlist.append
267 for (element, filternum) in lutlist:
271 append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
273 append(str(item[element]).replace("&", "&").replace("<", "<").replace('"', '"').replace(">", ">"))
275 append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
277 append(str(item[element]))
278 # (this will be done in c++ later!)
279 return ''.join(strlist)
281 text = property(getText)
283 class webifHandler(ContentHandler):
284 def __init__(self, session,request):
288 self.session = session
290 self.request = request
292 def startElement(self, name, attrs):
293 if name == "e2:screen":
294 self.screen = eval(attrs["name"])(self.session,self.request) # fixme
295 self.screens.append(self.screen)
298 if name[:3] == "e2:":
301 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
305 tag = ''.join(tag)#.encode('utf-8')
309 elif self.mode == 1: # expect "<e2:element>"
310 assert name == "e2:element", "found %s instead of e2:element" % name
311 source = attrs["source"]
312 self.source_id = str(attrs.get("id", source))
313 self.source = self.screen[source]
314 self.is_streaming = "streaming" in attrs
315 elif self.mode == 2: # expect "<e2:convert>"
316 if name[:3] == "e2:":
317 assert name == "e2:convert"
319 ctype = attrs["type"]
321 # TODO: we need something better here
322 if ctype[:4] == "web:": # for now
323 self.converter = eval(ctype[4:])
326 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
328 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
333 assert name == "e2:item", "found %s instead of e2:item!" % name
334 assert "name" in attrs, "e2:item must have a name= attribute!"
335 filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4}[attrs.get("filter", "")]
336 self.sub.append(ListItem(attrs["name"], filter))
338 def endElement(self, name):
339 if name == "e2:screen":
343 tag = "</" + name + ">"
346 elif self.mode == 2 and name[:3] != "e2:":
348 elif self.mode == 2: # closed 'convert' -> sub
349 if len(self.sub) == 1:
350 self.sub = self.sub[0]
351 c = self.converter(self.sub)
352 c.connect(self.source)
355 elif self.mode == 1: # closed 'element'
356 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
357 if not self.is_streaming:
358 c = OneTimeElement(self.source_id)
360 c = StreamingElement(self.source_id)
362 c.connect(self.source)
364 self.screen.renderer.append(c)
367 if name[:3] == "e2:":
370 def processingInstruction(self, target, data):
371 self.res.append('<?' + target + ' ' + data + '>')
373 def characters(self, ch):
374 ch = ch.encode('utf-8')
380 def startEntity(self, name):
381 self.res.append('&' + name + ';');
384 for screen in self.screens:
388 print "screen cleanup!"
389 for screen in self.screens:
394 def renderPage(stream, path, req, session):
396 # read in the template, create required screens
397 # we don't have persistense yet.
398 # if we had, this first part would only be done once.
399 handler = webifHandler(session,req)
400 parser = make_parser()
401 parser.setFeature(feature_namespaces, 0)
402 parser.setContentHandler(handler)
403 print "__file__: ",__file__
405 parser.parse(open(util.sibpath(__file__, path)))
407 # by default, we have non-streaming pages
410 # first, apply "commands" (aka. URL argument)
411 for x in handler.res:
412 if isinstance(x, Element):
413 x.handleCommand(req.args)
417 # now, we have a list with static texts mixed
418 # with non-static Elements.
419 # flatten this list, write into the stream.
420 for x in handler.res:
421 if isinstance(x, Element):
422 if isinstance(x, StreamingElement):
430 from twisted.internet import reactor
432 reactor.callLater(3, ping, s)
434 # if we met a "StreamingElement", there is at least one
435 # element which wants to output data more than once,
436 # i.e. on host-originated changes.
437 # in this case, don't finish yet, don't cleanup yet,
438 # but instead do that when the client disconnects.
444 # you *need* something which constantly sends something in a regular interval,
445 # in order to detect disconnected clients.
446 # i agree that this "ping" sucks terrible, so better be sure to have something
447 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
449 stream.closed_callback = lambda: handler.cleanup()