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
26 from WebComponents.Sources.Settings import Settings
27 from WebComponents.Sources.SubServices import SubServices
28 from WebComponents.Sources.ParentControl import ParentControl
29 from WebComponents.Sources.About import About
31 from WebComponents.Sources.RequestData import RequestData
32 from Components.Sources.FrontendStatus import FrontendStatus
34 from Components.Converter.Converter import Converter
36 from Components.Element import Element
38 from xml.sax import make_parser
39 from xml.sax.handler import ContentHandler, feature_namespaces
40 from twisted.python import util
44 # prototype of the new web frontend template system.
46 class WebScreen(Screen):
47 def __init__(self, session,request):
48 Screen.__init__(self, session)
49 self.stand_alone = True
50 self.request = request
52 class TestScreen(InfoBarServiceName, InfoBarEvent,InfoBarTuner, WebScreen):
53 def __init__(self, session,request):
54 WebScreen.__init__(self, session,request)
55 InfoBarServiceName.__init__(self)
56 InfoBarEvent.__init__(self)
57 InfoBarTuner.__init__(self)
58 self["CurrentTime"] = Clock()
59 # self["TVSystem"] = Config(config.av.tvsystem)
60 # self["OSDLanguage"] = Config(config.osd.language)
61 # self["FirstRun"] = Config(config.misc.firstrun)
62 from enigma import eServiceReference
63 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')
64 self["ServiceList"] = ServiceList(fav, command_func = self.zapTo, validate_commands=False)
65 self["ServiceListBrowse"] = ServiceList(fav, command_func = self.browseTo, validate_commands=False)
67 self["ParentControlList"] = ParentControl(session)
68 self["SubServices"] = SubServices(session)
69 self["Volume"] = Volume(session)
70 self["EPGTITLE"] = EPG(session,func=EPG.TITLE)
71 self["EPGSERVICE"] = EPG(session,func=EPG.SERVICE)
72 self["EPGNOW"] = EPG(session,func=EPG.NOW)
73 self["TimerList"] = Timer(session,func = Timer.LIST)
74 self["TimerAddEventID"] = Timer(session,func = Timer.ADDBYID)
75 self["TimerAdd"] = Timer(session,func = Timer.ADD)
76 self["TimerDel"] = Timer(session,func = Timer.DEL)
77 self["TimerChange"] = Timer(session,func = Timer.CHANGE)
78 self["TimerListWrite"] = Timer(session,func = Timer.WRITE)
79 self["TVBrowser"] = Timer(session,func = Timer.TVBROWSER)
80 self["RecordNow"] = Timer(session,func = Timer.RECNOW)
81 self["MovieList"] = Movie(session,func = Movie.LIST)
82 self["MovieFileDel"] = Movie(session,func = Movie.DEL)
83 self["Volume"] = Volume(session)
84 self["Message"] = Message(session)
85 self["PowerState"] = PowerState(session)
86 self["RemoteControl"] = RemoteControl(session)
87 self["Settings"] = Settings(session)
89 self["About"] = About(session)
91 def browseTo(self, reftobrowse):
92 self["ServiceListBrowse"].root = reftobrowse
94 def zapTo(self, reftozap):
95 from Components.config import config
96 pc = config.ParentalControl.configured.value
98 config.ParentalControl.configured.value = False
99 self.session.nav.playService(reftozap)
101 config.ParentalControl.configured.value = pc
103 switsching config.ParentalControl.configured.value
104 ugly, but necessary :(
107 # TODO: (really.) put screens into own files.
108 class Streaming(WebScreen):
109 def __init__(self, session,request):
110 WebScreen.__init__(self, session,request)
111 from Components.Sources.StreamService import StreamService
112 self["StreamService"] = StreamService(self.session.nav)
114 class StreamingM3U(WebScreen):
115 def __init__(self, session,request):
116 WebScreen.__init__(self, session,request)
117 from Components.Sources.StaticText import StaticText
118 from Components.Sources.Config import Config
119 from Components.config import config
120 self["ref"] = StaticText()
121 self["localip"] = RequestData(request,what=RequestData.HOST)
123 class TsM3U(WebScreen):
124 def __init__(self, session,request):
125 WebScreen.__init__(self, session,request)
126 from Components.Sources.StaticText import StaticText
127 from Components.Sources.Config import Config
128 from Components.config import config
129 self["file"] = StaticText()
130 self["localip"] = RequestData(request,what=RequestData.HOST)
132 class RestartTwisted(WebScreen):
133 def __init__(self, session,request):
134 WebScreen.__init__(self, session,request)
136 plugin.restartWebserver()
138 class GetPid(WebScreen):
139 def __init__(self, session,request):
140 WebScreen.__init__(self, session,request)
141 from Components.Sources.StaticText import StaticText
142 from enigma import iServiceInformation
143 pids = self.session.nav.getCurrentService()
145 pidinfo = pids.info()
146 VPID = hex(pidinfo.getInfo(iServiceInformation.sVideoPID))
147 APID = hex(pidinfo.getInfo(iServiceInformation.sAudioPID))
148 PPID = hex(pidinfo.getInfo(iServiceInformation.sPMTPID))
149 self["pids"] = StaticText("%s,%s,%s"%(PPID.lstrip("0x"),VPID.lstrip("0x"),APID.lstrip("0x")))
150 self["localip"] = RequestData(request,what=RequestData.HOST)
153 # implements the 'render'-call.
154 # this will act as a downstream_element, like a renderer.
155 class OneTimeElement(Element):
156 def __init__(self, id):
157 Element.__init__(self)
160 # CHECKME: is this ok performance-wise?
161 def handleCommand(self, args):
162 if self.source_id.find(",") >=0:
163 paramlist = self.source_id.split(",")
165 for key in paramlist:
166 arg = args.get(key, [])
170 list[key] = "".join(arg)
173 self.source.handleCommand(list)
175 for c in args.get(self.source_id, []):
176 self.source.handleCommand(c)
178 def render(self, stream):
179 t = self.source.getHTML(self.source_id)
197 class StreamingElement(OneTimeElement):
198 def __init__(self, id):
199 OneTimeElement.__init__(self, id)
202 def changed(self, what):
204 self.render(self.stream)
206 def setStream(self, stream):
209 # a to-be-filled list item
211 def __init__(self, name, filternum):
213 self.filternum = filternum
215 class TextToHTML(Converter):
216 def __init__(self, arg):
217 Converter.__init__(self, arg)
219 def getHTML(self, id):
220 return self.source.text # encode & etc. here!
222 class ReturnEmptyXML(Converter):
223 def __init__(self, arg):
224 Converter.__init__(self, arg)
226 def getHTML(self, id):
227 return "<rootElement></rootElement>"
229 # a null-output. Useful if you only want to issue a command.
230 class Null(Converter):
231 def __init__(self, arg):
232 Converter.__init__(self, arg)
234 def getHTML(self, id):
237 class JavascriptUpdate(Converter):
238 def __init__(self, arg):
239 Converter.__init__(self, arg)
241 def getHTML(self, id):
242 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
243 # all other will replace this in JS
244 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '°'))
246 # the performant 'listfiller'-engine (plfe)
247 class ListFiller(Converter):
248 def __init__(self, arg):
249 Converter.__init__(self, arg)
253 lut = self.source.lut
254 conv_args = self.converter_arguments
256 # now build a ["string", 1, "string", 2]-styled list, with indices into the
257 # list to avoid lookup of item name for each entry
258 lutlist = [ isinstance(element, basestring) and (element, None) or (lut[element.name], element.filternum) for element in conv_args ]
260 # now, for the huge list, do:
262 append = strlist.append
264 for (element, filternum) in lutlist:
268 append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
270 append(str(item[element]).replace("&", "&").replace("<", "<").replace('"', '"').replace(">", ">"))
272 append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
274 append(str(item[element]))
275 # (this will be done in c++ later!)
276 return ''.join(strlist)
278 text = property(getText)
280 class webifHandler(ContentHandler):
281 def __init__(self, session,request):
285 self.session = session
287 self.request = request
289 def startElement(self, name, attrs):
290 if name == "e2:screen":
291 self.screen = eval(attrs["name"])(self.session,self.request) # fixme
292 self.screens.append(self.screen)
295 if name[:3] == "e2:":
298 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
302 tag = ''.join(tag)#.encode('utf-8')
306 elif self.mode == 1: # expect "<e2:element>"
307 assert name == "e2:element", "found %s instead of e2:element" % name
308 source = attrs["source"]
309 self.source_id = str(attrs.get("id", source))
310 self.source = self.screen[source]
311 self.is_streaming = "streaming" in attrs
312 elif self.mode == 2: # expect "<e2:convert>"
313 if name[:3] == "e2:":
314 assert name == "e2:convert"
316 ctype = attrs["type"]
318 # TODO: we need something better here
319 if ctype[:4] == "web:": # for now
320 self.converter = eval(ctype[4:])
323 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
325 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
330 assert name == "e2:item", "found %s instead of e2:item!" % name
331 assert "name" in attrs, "e2:item must have a name= attribute!"
332 filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4}[attrs.get("filter", "")]
333 self.sub.append(ListItem(attrs["name"], filter))
335 def endElement(self, name):
336 if name == "e2:screen":
340 tag = "</" + name + ">"
343 elif self.mode == 2 and name[:3] != "e2:":
345 elif self.mode == 2: # closed 'convert' -> sub
346 if len(self.sub) == 1:
347 self.sub = self.sub[0]
348 c = self.converter(self.sub)
349 c.connect(self.source)
352 elif self.mode == 1: # closed 'element'
353 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
354 if not self.is_streaming:
355 c = OneTimeElement(self.source_id)
357 c = StreamingElement(self.source_id)
359 c.connect(self.source)
361 self.screen.renderer.append(c)
364 if name[:3] == "e2:":
367 def processingInstruction(self, target, data):
368 self.res.append('<?' + target + ' ' + data + '>')
370 def characters(self, ch):
371 ch = ch.encode('utf-8')
377 def startEntity(self, name):
378 self.res.append('&' + name + ';');
381 for screen in self.screens:
385 print "screen cleanup!"
386 for screen in self.screens:
391 def renderPage(stream, path, req, session):
393 # read in the template, create required screens
394 # we don't have persistense yet.
395 # if we had, this first part would only be done once.
396 handler = webifHandler(session,req)
397 parser = make_parser()
398 parser.setFeature(feature_namespaces, 0)
399 parser.setContentHandler(handler)
400 parser.parse(open(util.sibpath(__file__, path)))
402 # by default, we have non-streaming pages
405 # first, apply "commands" (aka. URL argument)
406 for x in handler.res:
407 if isinstance(x, Element):
408 x.handleCommand(req.args)
412 # now, we have a list with static texts mixed
413 # with non-static Elements.
414 # flatten this list, write into the stream.
415 for x in handler.res:
416 if isinstance(x, Element):
417 if isinstance(x, StreamingElement):
425 from twisted.internet import reactor
427 reactor.callLater(3, ping, s)
429 # if we met a "StreamingElement", there is at least one
430 # element which wants to output data more than once,
431 # i.e. on host-originated changes.
432 # in this case, don't finish yet, don't cleanup yet,
433 # but instead do that when the client disconnects.
439 # you *need* something which constantly sends something in a regular interval,
440 # in order to detect disconnected clients.
441 # i agree that this "ping" sucks terrible, so better be sure to have something
442 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
444 stream.closed_callback = lambda: handler.cleanup()