3 # OK, this is more than a proof of concept
6 # - screens need to be defined somehow else.
7 # I don't know how, yet. Probably each in an own file.
8 # - more components, like the channellist
9 # - better error handling
10 # - use namespace parser
12 from Screens.Screen import Screen
13 from Tools.Import import my_import
16 from Screens.InfoBarGenerics import InfoBarServiceName, InfoBarEvent, InfoBarTuner
18 from Components.Sources.Clock import Clock
19 from Components.Sources.ServiceList import ServiceList
21 from WebComponents.Sources.ServiceListRecursive import ServiceListRecursive
22 from WebComponents.Sources.Volume import Volume
23 from WebComponents.Sources.EPG import EPG
24 from WebComponents.Sources.Timer import Timer
25 from WebComponents.Sources.Movie import Movie
26 from WebComponents.Sources.Message import Message
27 from WebComponents.Sources.PowerState import PowerState
28 from WebComponents.Sources.RemoteControl import RemoteControl
29 from WebComponents.Sources.Settings import Settings
30 from WebComponents.Sources.SubServices import SubServices
31 from WebComponents.Sources.ParentControl import ParentControl
32 from WebComponents.Sources.About import About
33 from WebComponents.Sources.RequestData import RequestData
34 from WebComponents.Sources.AudioTracks import AudioTracks
36 from Components.Sources.FrontendStatus import FrontendStatus
38 from Components.Converter.Converter import Converter
40 from Components.Element import Element
42 from xml.sax import make_parser
43 from xml.sax.handler import ContentHandler, feature_namespaces
45 from twisted.python import util
50 # prototype of the new web frontend template system.
52 class WebScreen(Screen):
53 def __init__(self, session, request):
54 Screen.__init__(self, session)
55 self.stand_alone = True
56 self.request = request
60 class TestScreen(InfoBarServiceName, InfoBarEvent,InfoBarTuner, WebScreen):
61 def __init__(self, session,request):
62 WebScreen.__init__(self, session,request)
63 InfoBarServiceName.__init__(self)
64 InfoBarEvent.__init__(self)
65 InfoBarTuner.__init__(self)
66 self["CurrentTime"] = Clock()
67 # self["TVSystem"] = Config(config.av.tvsystem)
68 # self["OSDLanguage"] = Config(config.osd.language)
69 # self["FirstRun"] = Config(config.misc.firstrun)
70 from enigma import eServiceReference
71 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')
72 self["SwitchService"] = ServiceList(fav, command_func = self.zapTo, validate_commands=False)
73 self["ServiceList"] = ServiceList(fav, command_func = self.getServiceList, validate_commands=False)
74 self["ServiceListRecursive"] = ServiceListRecursive(session, func=ServiceListRecursive.FETCH)
75 self["ParentControlList"] = ParentControl(session)
76 self["SubServices"] = SubServices(session)
77 self["Volume"] = Volume(session)
78 self["EPGTITLE"] = EPG(session,func=EPG.TITLE)
79 self["EPGSERVICE"] = EPG(session,func=EPG.SERVICE)
80 self["EPGNOW"] = EPG(session,func=EPG.NOW)
81 self["TimerList"] = Timer(session,func = Timer.LIST)
82 self["TimerAddEventID"] = Timer(session,func = Timer.ADDBYID)
83 self["TimerAdd"] = Timer(session,func = Timer.ADD)
84 self["TimerDel"] = Timer(session,func = Timer.DEL)
85 self["TimerChange"] = Timer(session,func = Timer.CHANGE)
86 self["TimerListWrite"] = Timer(session,func = Timer.WRITE)
87 self["TVBrowser"] = Timer(session,func = Timer.TVBROWSER)
88 self["RecordNow"] = Timer(session,func = Timer.RECNOW)
89 self["MovieList"] = Movie(session,func = Movie.LIST)
90 self["MovieFileDel"] = Movie(session,func = Movie.DEL)
91 self["MovieTags"] = Movie(session,func = Movie.TAGS)
92 self["Volume"] = Volume(session)
93 self["Message"] = Message(session)
94 self["PowerState"] = PowerState(session)
95 self["RemoteControl"] = RemoteControl(session)
96 self["Settings"] = Settings(session)
97 self["AudioTracks"] = AudioTracks(session)
99 self["About"] = About(session)
101 def getServiceList(self, sRef):
102 self["ServiceList"].root = sRef
104 def zapTo(self, reftozap):
105 from Components.config import config
106 pc = config.ParentalControl.configured.value
108 config.ParentalControl.configured.value = False
109 self.session.nav.playService(reftozap)
111 config.ParentalControl.configured.value = pc
113 switching config.ParentalControl.configured.value
114 ugly, but necessary :(
117 # TODO: (really.) put screens into own files.
118 class Streaming(WebScreen):
119 def __init__(self, session,request):
120 WebScreen.__init__(self, session,request)
121 from Components.Sources.StreamService import StreamService
122 self["StreamService"] = StreamService(self.session.nav)
124 class StreamingM3U(WebScreen):
125 def __init__(self, session,request):
126 WebScreen.__init__(self, session,request)
127 from Components.Sources.StaticText import StaticText
128 from Components.Sources.Config import Config
129 from Components.config import config
130 self["ref"] = StaticText()
131 self["localip"] = RequestData(request,what=RequestData.HOST)
133 class TsM3U(WebScreen):
134 def __init__(self, session,request):
135 WebScreen.__init__(self, session,request)
136 from Components.Sources.StaticText import StaticText
137 from Components.Sources.Config import Config
138 from Components.config import config
139 self["file"] = StaticText()
140 self["localip"] = RequestData(request,what=RequestData.HOST)
142 class RestartTwisted(WebScreen):
143 def __init__(self, session,request):
144 WebScreen.__init__(self, session,request)
146 plugin.restartWebserver()
148 class GetPid(WebScreen):
149 def __init__(self, session,request):
150 WebScreen.__init__(self, session,request)
151 from Components.Sources.StaticText import StaticText
152 from enigma import iServiceInformation
153 pids = self.session.nav.getCurrentService()
155 pidinfo = pids.info()
156 VPID = hex(pidinfo.getInfo(iServiceInformation.sVideoPID))
157 APID = hex(pidinfo.getInfo(iServiceInformation.sAudioPID))
158 PPID = hex(pidinfo.getInfo(iServiceInformation.sPMTPID))
159 self["pids"] = StaticText("%s,%s,%s"%(PPID.lstrip("0x"),VPID.lstrip("0x"),APID.lstrip("0x")))
160 self["localip"] = RequestData(request,what=RequestData.HOST)
163 # implements the 'render'-call.
164 # this will act as a downstream_element, like a renderer.
165 class OneTimeElement(Element):
166 def __init__(self, id):
167 Element.__init__(self)
170 # CHECKME: is this ok performance-wise?
171 def handleCommand(self, args):
172 if self.source_id.find(",") >=0:
173 paramlist = self.source_id.split(",")
175 for key in paramlist:
176 arg = args.get(key, [])
180 list[key] = "".join(arg)
183 self.source.handleCommand(list)
185 for c in args.get(self.source_id, []):
186 self.source.handleCommand(c)
188 def render(self, stream):
189 t = self.source.getHTML(self.source_id)
207 class StreamingElement(OneTimeElement):
208 def __init__(self, id):
209 OneTimeElement.__init__(self, id)
212 def changed(self, what):
214 self.render(self.stream)
216 def setStream(self, stream):
219 # a to-be-filled list item
221 def __init__(self, name, filternum):
223 self.filternum = filternum
225 class TextToHTML(Converter):
226 def __init__(self, arg):
227 Converter.__init__(self, arg)
229 def getHTML(self, id):
230 return self.source.text # encode & etc. here!
232 class TextToURL(Converter):
233 def __init__(self, arg):
234 Converter.__init__(self, arg)
236 def getHTML(self, id):
237 return self.source.text.replace(" ","%20")
239 class ReturnEmptyXML(Converter):
240 def __init__(self, arg):
241 Converter.__init__(self, arg)
243 def getHTML(self, id):
244 return "<rootElement></rootElement>"
246 # a null-output. Useful if you only want to issue a command.
247 class Null(Converter):
248 def __init__(self, arg):
249 Converter.__init__(self, arg)
251 def getHTML(self, id):
254 class JavascriptUpdate(Converter):
255 def __init__(self, arg):
256 Converter.__init__(self, arg)
258 def getHTML(self, id):
259 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
260 # all other will replace this in JS
261 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '°'))
263 # the performant 'listfiller'-engine (plfe)
264 class ListFiller(Converter):
265 def __init__(self, arg):
266 Converter.__init__(self, arg)
270 lut = self.source.lut
271 conv_args = self.converter_arguments
273 # now build a ["string", 1, "string", 2]-styled list, with indices into the
274 # list to avoid lookup of item name for each entry
275 lutlist = [ isinstance(element, basestring) and (element, None) or (lut[element.name], element.filternum) for element in conv_args ]
277 # now, for the huge list, do:
279 append = strlist.append
281 for (element, filternum) in lutlist:
285 append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
287 append(str(item[element]).replace("&", "&").replace("<", "<").replace('"', '"').replace(">", ">"))
289 append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
291 append(str(item[element]))
292 # (this will be done in c++ later!)
293 return ''.join(strlist)
295 text = property(getText)
297 class webifHandler(ContentHandler):
298 def __init__(self, session,request):
302 self.session = session
304 self.request = request
306 def startElement(self, name, attrs):
307 if name == "e2:screen":
308 self.screen = eval(attrs["name"])(self.session,self.request) # fixme
309 self.screens.append(self.screen)
312 if name[:3] == "e2:":
315 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
319 tag = ''.join(tag)#.encode('utf-8')
323 elif self.mode == 1: # expect "<e2:element>"
324 assert name == "e2:element", "found %s instead of e2:element" % name
325 source = attrs["source"]
326 self.source_id = str(attrs.get("id", source))
327 self.source = self.screen[source]
328 self.is_streaming = "streaming" in attrs
329 elif self.mode == 2: # expect "<e2:convert>"
330 if name[:3] == "e2:":
331 assert name == "e2:convert"
333 ctype = attrs["type"]
335 # TODO: we need something better here
336 if ctype[:4] == "web:": # for now
337 self.converter = eval(ctype[4:])
340 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
342 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
347 assert name == "e2:item", "found %s instead of e2:item!" % name
348 assert "name" in attrs, "e2:item must have a name= attribute!"
349 filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4}[attrs.get("filter", "")]
350 self.sub.append(ListItem(attrs["name"], filter))
352 def endElement(self, name):
353 if name == "e2:screen":
357 tag = "</" + name + ">"
360 elif self.mode == 2 and name[:3] != "e2:":
362 elif self.mode == 2: # closed 'convert' -> sub
363 if len(self.sub) == 1:
364 self.sub = self.sub[0]
365 c = self.converter(self.sub)
366 c.connect(self.source)
369 elif self.mode == 1: # closed 'element'
370 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
371 if not self.is_streaming:
372 c = OneTimeElement(self.source_id)
374 c = StreamingElement(self.source_id)
376 c.connect(self.source)
378 self.screen.renderer.append(c)
381 if name[:3] == "e2:":
384 def processingInstruction(self, target, data):
385 self.res.append('<?' + target + ' ' + data + '>')
387 def characters(self, ch):
388 ch = ch.encode('utf-8')
394 def startEntity(self, name):
395 self.res.append('&' + name + ';');
398 for screen in self.screens:
402 print "screen cleanup!"
403 for screen in self.screens:
408 def renderPage(stream, path, req, session):
410 # read in the template, create required screens
411 # we don't have persistense yet.
412 # if we had, this first part would only be done once.
413 handler = webifHandler(session,req)
414 parser = make_parser()
415 parser.setFeature(feature_namespaces, 0)
416 parser.setContentHandler(handler)
417 parser.parse(open(util.sibpath(__file__, path)))
419 # by default, we have non-streaming pages
422 # first, apply "commands" (aka. URL argument)
423 for x in handler.res:
424 if isinstance(x, Element):
425 x.handleCommand(req.args)
429 # now, we have a list with static texts mixed
430 # with non-static Elements.
431 # flatten this list, write into the stream.
432 for x in handler.res:
433 if isinstance(x, Element):
434 if isinstance(x, StreamingElement):
442 from twisted.internet import reactor
444 reactor.callLater(3, ping, s)
446 # if we met a "StreamingElement", there is at least one
447 # element which wants to output data more than once,
448 # i.e. on host-originated changes.
449 # in this case, don't finish yet, don't cleanup yet,
450 # but instead do that when the client disconnects.
456 # you *need* something which constantly sends something in a regular interval,
457 # in order to detect disconnected clients.
458 # i agree that this "ping" sucks terrible, so better be sure to have something
459 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
461 stream.closed_callback = lambda: handler.cleanup()