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
11 from enigma import eServiceReference
13 from Screens.Screen import Screen
14 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
35 from WebComponents.Sources.WAPfunctions import WAPfunctions
37 from Components.Sources.FrontendStatus import FrontendStatus
39 from Components.Converter.Converter import Converter
41 from Components.Element import Element
43 from xml.sax import make_parser
44 from xml.sax.handler import ContentHandler, feature_namespaces
46 from twisted.python import util
51 # prototype of the new web frontend template system.
53 class WebScreen(Screen):
54 def __init__(self, session, request):
55 Screen.__init__(self, session)
56 self.stand_alone = True
57 self.request = request
60 class DummyWebScreen(WebScreen):
61 #use it, if you dont need any source, just to can do a static file with an xml-file
62 def __init__(self, session,request):
63 WebScreen.__init__(self, session,request)
65 class UpdateWebScreen(InfoBarServiceName, InfoBarEvent,InfoBarTuner,WebScreen):
66 def __init__(self, session,request):
67 WebScreen.__init__(self, session,request)
68 InfoBarServiceName.__init__(self)
69 InfoBarEvent.__init__(self)
70 InfoBarTuner.__init__(self)
71 self["CurrentTime"] = Clock()
72 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')
78 class MessageWebScreen(WebScreen):
79 def __init__(self, session,request):
80 WebScreen.__init__(self, session,request)
81 self["Message"] = Message(session)
83 class AudioWebScreen(WebScreen):
84 def __init__(self, session,request):
85 WebScreen.__init__(self, session,request)
86 self["AudioTracks"] = AudioTracks(session)
88 class AboutWebScreen(WebScreen):
89 def __init__(self, session,request):
90 WebScreen.__init__(self, session,request)
91 self["About"] = About(session)
93 class VolumeWebScreen(WebScreen):
94 def __init__(self, session,request):
95 WebScreen.__init__(self, session,request)
96 self["Volume"] = Volume(session)
98 class SettingsWebScreen(WebScreen):
99 def __init__(self, session,request):
100 WebScreen.__init__(self, session,request)
101 self["Settings"] = Settings(session)
103 class SubServiceWebScreen(WebScreen):
104 def __init__(self, session,request):
105 WebScreen.__init__(self, session,request)
106 self["SubServices"] = SubServices(session)
108 class ServiceWebScreen(WebScreen):
109 def __init__(self, session,request):
110 WebScreen.__init__(self, session,request)
111 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')
112 self["SwitchService"] = ServiceList(fav, command_func = self.zapTo, validate_commands=False)
113 self["ServiceList"] = ServiceList(fav, command_func = self.getServiceList, validate_commands=False)
114 self["ServiceListRecursive"] = ServiceListRecursive(session, func=ServiceListRecursive.FETCH)
116 def getServiceList(self, sRef):
117 self["ServiceList"].root = sRef
119 def zapTo(self, reftozap):
120 from Components.config import config
121 pc = config.ParentalControl.configured.value
123 config.ParentalControl.configured.value = False
124 self.session.nav.playService(reftozap)
126 config.ParentalControl.configured.value = pc
128 switching config.ParentalControl.configured.value
129 ugly, but necessary :(
132 class EPGWebScreen(WebScreen):
133 def __init__(self, session,request):
134 WebScreen.__init__(self, session,request)
135 self["EPGTITLE"] = EPG(session,func=EPG.TITLE)
136 self["EPGSERVICE"] = EPG(session,func=EPG.SERVICE)
137 self["EPGNOW"] = EPG(session,func=EPG.NOW)
139 class MovieWebScreen(WebScreen):
140 def __init__(self, session,request):
141 WebScreen.__init__(self, session,request)
142 from Components.MovieList import MovieList
143 from Tools.Directories import resolveFilename,SCOPE_HDD
144 movielist = MovieList(eServiceReference("2:0:1:0:0:0:0:0:0:0:" + resolveFilename(SCOPE_HDD)))
145 self["MovieList"] = Movie(session,movielist,func = Movie.LIST)
146 self["MovieFileDel"] = Movie(session,movielist,func = Movie.DEL)
147 self["MovieTags"] = Movie(session,movielist,func = Movie.TAGS)
150 class TimerWebScreen(WebScreen):
151 def __init__(self, session,request):
152 WebScreen.__init__(self, session,request)
153 self["TimerList"] = Timer(session,func = Timer.LIST)
154 self["TimerAddEventID"] = Timer(session,func = Timer.ADDBYID)
155 self["TimerAdd"] = Timer(session,func = Timer.ADD)
156 self["TimerDel"] = Timer(session,func = Timer.DEL)
157 self["TimerChange"] = Timer(session,func = Timer.CHANGE)
158 self["TimerListWrite"] = Timer(session,func = Timer.WRITE)
159 self["TVBrowser"] = Timer(session,func = Timer.TVBROWSER)
160 self["RecordNow"] = Timer(session,func = Timer.RECNOW)
162 class RemoteWebScreen(WebScreen):
163 def __init__(self, session,request):
164 WebScreen.__init__(self, session,request)
165 self["RemoteControl"] = RemoteControl(session)
167 class PowerWebScreen(WebScreen):
168 def __init__(self, session,request):
169 WebScreen.__init__(self, session,request)
170 self["PowerState"] = PowerState(session)
172 class ParentControlWebScreen(WebScreen):
173 def __init__(self, session,request):
174 WebScreen.__init__(self, session,request)
175 self["ParentControlList"] = ParentControl(session)
177 class WAPWebScreen(WebScreen):
178 def __init__(self, session,request):
179 WebScreen.__init__(self, session,request)
180 self["WAPFillOptionListSyear"] = WAPfunctions(session,func = WAPfunctions.FILLOPTIONLIST)
181 self["WAPFillOptionListSday"] = WAPfunctions(session,func = WAPfunctions.FILLOPTIONLIST)
182 self["WAPFillOptionListSmonth"] = WAPfunctions(session,func = WAPfunctions.FILLOPTIONLIST)
183 self["WAPFillOptionListShour"] = WAPfunctions(session,func = WAPfunctions.FILLOPTIONLIST)
184 self["WAPFillOptionListSmin"] = WAPfunctions(session,func = WAPfunctions.FILLOPTIONLIST)
186 self["WAPFillOptionListEyear"] = WAPfunctions(session,func = WAPfunctions.FILLOPTIONLIST)
187 self["WAPFillOptionListEday"] = WAPfunctions(session,func = WAPfunctions.FILLOPTIONLIST)
188 self["WAPFillOptionListEmonth"] = WAPfunctions(session,func = WAPfunctions.FILLOPTIONLIST)
189 self["WAPFillOptionListEhour"] = WAPfunctions(session,func = WAPfunctions.FILLOPTIONLIST)
190 self["WAPFillOptionListEmin"] = WAPfunctions(session,func = WAPfunctions.FILLOPTIONLIST)
192 self["WAPFillOptionListRepeated"] = WAPfunctions(session,func = WAPfunctions.REPEATED)
193 self["WAPServiceList"] = WAPfunctions(session, func = WAPfunctions.SERVICELIST)
195 class StreamingWebScreen(WebScreen):
196 def __init__(self, session,request):
197 WebScreen.__init__(self, session,request)
198 from Components.Sources.StreamService import StreamService
199 self["StreamService"] = StreamService(self.session.nav)
201 class M3UStreamingWebScreen(WebScreen):
202 def __init__(self, session,request):
203 WebScreen.__init__(self, session,request)
204 from Components.Sources.StaticText import StaticText
205 from Components.Sources.Config import Config
206 from Components.config import config
207 self["ref"] = StaticText()
208 self["localip"] = RequestData(request,what=RequestData.HOST)
210 class TsM3U(WebScreen):
211 def __init__(self, session,request):
212 WebScreen.__init__(self, session,request)
213 from Components.Sources.StaticText import StaticText
214 from Components.Sources.Config import Config
215 from Components.config import config
216 self["file"] = StaticText()
217 self["localip"] = RequestData(request,what=RequestData.HOST)
219 class RestartWebScreen(WebScreen):
220 def __init__(self, session,request):
221 WebScreen.__init__(self, session,request)
223 plugin.restartWebserver()
225 class GetPid(WebScreen):
226 def __init__(self, session,request):
227 WebScreen.__init__(self, session,request)
228 from Components.Sources.StaticText import StaticText
229 from enigma import iServiceInformation
230 pids = self.session.nav.getCurrentService()
232 pidinfo = pids.info()
233 VPID = hex(pidinfo.getInfo(iServiceInformation.sVideoPID))
234 APID = hex(pidinfo.getInfo(iServiceInformation.sAudioPID))
235 PPID = hex(pidinfo.getInfo(iServiceInformation.sPMTPID))
236 self["pids"] = StaticText("%s,%s,%s"%(PPID.lstrip("0x"),VPID.lstrip("0x"),APID.lstrip("0x")))
237 self["localip"] = RequestData(request,what=RequestData.HOST)
240 # implements the 'render'-call.
241 # this will act as a downstream_element, like a renderer.
242 class OneTimeElement(Element):
243 def __init__(self, id):
244 Element.__init__(self)
247 # CHECKME: is this ok performance-wise?
248 def handleCommand(self, args):
249 if self.source_id.find(",") >=0:
250 paramlist = self.source_id.split(",")
252 for key in paramlist:
253 arg = args.get(key, [])
257 list[key] = "".join(arg)
260 self.source.handleCommand(list)
262 for c in args.get(self.source_id, []):
263 self.source.handleCommand(c)
265 def render(self, stream):
266 t = self.source.getHTML(self.source_id)
284 class StreamingElement(OneTimeElement):
285 def __init__(self, id):
286 OneTimeElement.__init__(self, id)
289 def changed(self, what):
291 self.render(self.stream)
293 def setStream(self, stream):
296 # a to-be-filled list item
298 def __init__(self, name, filternum):
300 self.filternum = filternum
302 class TextToHTML(Converter):
303 def __init__(self, arg):
304 Converter.__init__(self, arg)
306 def getHTML(self, id):
307 return self.source.text # encode & etc. here!
309 class TextToURL(Converter):
310 def __init__(self, arg):
311 Converter.__init__(self, arg)
313 def getHTML(self, id):
314 return self.source.text.replace(" ","%20")
316 class ReturnEmptyXML(Converter):
317 def __init__(self, arg):
318 Converter.__init__(self, arg)
320 def getHTML(self, id):
321 return "<rootElement></rootElement>"
323 # a null-output. Useful if you only want to issue a command.
324 class Null(Converter):
325 def __init__(self, arg):
326 Converter.__init__(self, arg)
328 def getHTML(self, id):
331 class JavascriptUpdate(Converter):
332 def __init__(self, arg):
333 Converter.__init__(self, arg)
335 def getHTML(self, id):
336 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
337 # all other will replace this in JS
338 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '°'))
340 # the performant 'listfiller'-engine (plfe)
341 class ListFiller(Converter):
342 def __init__(self, arg):
343 Converter.__init__(self, arg)
344 # print "ListFiller-arg: ",arg
348 lut = self.source.lut
349 conv_args = self.converter_arguments
351 # now build a ["string", 1, "string", 2]-styled list, with indices into the
352 # list to avoid lookup of item name for each entry
353 lutlist = [ isinstance(element, basestring) and (element, None) or (lut[element.name], element.filternum) for element in conv_args ]
355 # now, for the huge list, do:
357 append = strlist.append
359 for (element, filternum) in lutlist:
363 append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
365 append(str(item[element]).replace("&", "&").replace("<", "<").replace('"', '"').replace(">", ">"))
367 append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
369 append(str(item[element]))
370 # (this will be done in c++ later!)
371 return ''.join(strlist)
373 text = property(getText)
375 class webifHandler(ContentHandler):
376 def __init__(self, session,request):
380 self.session = session
382 self.request = request
384 def startElement(self, name, attrs):
385 if name == "e2:screen":
386 self.screen = eval(attrs["name"])(self.session,self.request) # fixme
387 self.screens.append(self.screen)
390 if name[:3] == "e2:":
393 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
397 tag = ''.join(tag)#.encode('utf-8')
401 elif self.mode == 1: # expect "<e2:element>"
402 assert name == "e2:element", "found %s instead of e2:element" % name
403 source = attrs["source"]
404 self.source_id = str(attrs.get("id", source))
405 self.source = self.screen[source]
406 self.is_streaming = "streaming" in attrs
407 elif self.mode == 2: # expect "<e2:convert>"
408 if name[:3] == "e2:":
409 assert name == "e2:convert"
411 ctype = attrs["type"]
413 # TODO: we need something better here
414 if ctype[:4] == "web:": # for now
415 self.converter = eval(ctype[4:])
418 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
420 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
425 assert name == "e2:item", "found %s instead of e2:item!" % name
426 assert "name" in attrs, "e2:item must have a name= attribute!"
427 filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4}[attrs.get("filter", "")]
428 self.sub.append(ListItem(attrs["name"], filter))
430 def endElement(self, name):
431 if name == "e2:screen":
435 tag = "</" + name + ">"
438 elif self.mode == 2 and name[:3] != "e2:":
440 elif self.mode == 2: # closed 'convert' -> sub
441 if len(self.sub) == 1:
442 self.sub = self.sub[0]
443 c = self.converter(self.sub)
444 c.connect(self.source)
447 elif self.mode == 1: # closed 'element'
448 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
449 if not self.is_streaming:
450 c = OneTimeElement(self.source_id)
452 c = StreamingElement(self.source_id)
454 c.connect(self.source)
456 self.screen.renderer.append(c)
459 if name[:3] == "e2:":
462 def processingInstruction(self, target, data):
463 self.res.append('<?' + target + ' ' + data + '>')
465 def characters(self, ch):
466 ch = ch.encode('utf-8')
472 def startEntity(self, name):
473 self.res.append('&' + name + ';');
476 for screen in self.screens:
480 print "screen cleanup!"
481 for screen in self.screens:
486 def renderPage(stream, path, req, session):
488 # read in the template, create required screens
489 # we don't have persistense yet.
490 # if we had, this first part would only be done once.
491 handler = webifHandler(session,req)
492 parser = make_parser()
493 parser.setFeature(feature_namespaces, 0)
494 parser.setContentHandler(handler)
495 parser.parse(open(util.sibpath(__file__, path)))
497 # by default, we have non-streaming pages
500 # first, apply "commands" (aka. URL argument)
501 for x in handler.res:
502 if isinstance(x, Element):
503 x.handleCommand(req.args)
507 # now, we have a list with static texts mixed
508 # with non-static Elements.
509 # flatten this list, write into the stream.
510 for x in handler.res:
511 if isinstance(x, Element):
512 if isinstance(x, StreamingElement):
520 from twisted.internet import reactor
522 reactor.callLater(3, ping, s)
524 # if we met a "StreamingElement", there is at least one
525 # element which wants to output data more than once,
526 # i.e. on host-originated changes.
527 # in this case, don't finish yet, don't cleanup yet,
528 # but instead do that when the client disconnects.
534 # you *need* something which constantly sends something in a regular interval,
535 # in order to detect disconnected clients.
536 # i agree that this "ping" sucks terrible, so better be sure to have something
537 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
539 stream.closed_callback = lambda: handler.cleanup()