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
36 from WebComponents.Sources.MP import MP
37 from WebComponents.Sources.Files import Files
39 from Components.Sources.FrontendStatus import FrontendStatus
41 from Components.Converter.Converter import Converter
43 from Components.Element import Element
45 from xml.sax import make_parser
46 from xml.sax.handler import ContentHandler, feature_namespaces
48 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
59 class DummyWebScreen(WebScreen):
60 #use it, if you dont need any source, just to can do a static file with an xml-file
61 def __init__(self, session,request):
62 WebScreen.__init__(self, session,request)
64 class UpdateWebScreen(InfoBarServiceName, InfoBarEvent,InfoBarTuner,WebScreen):
65 def __init__(self, session,request):
66 WebScreen.__init__(self, session,request)
67 InfoBarServiceName.__init__(self)
68 InfoBarEvent.__init__(self)
69 InfoBarTuner.__init__(self)
70 self["CurrentTime"] = Clock()
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')
77 class MessageWebScreen(WebScreen):
78 def __init__(self, session,request):
79 WebScreen.__init__(self, session,request)
80 self["Message"] = Message(session,func = Message.PRINT)
81 self["GetAnswer"] = Message(session,func = Message.ANSWER)
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)
149 class MediaPlayerWebScreen(WebScreen):
150 def __init__(self, session,request):
151 WebScreen.__init__(self, session,request)
152 self["FileList"] = MP(session,func = MP.LIST)
153 self["PlayFile"] = MP(session,func = MP.PLAY)
154 self["Command"] = MP(session,func = MP.COMMAND)
156 class FilesWebScreen(WebScreen):
157 def __init__(self, session,request):
158 WebScreen.__init__(self, session,request)
159 self["DelFile"] = Files(session,func = Files.DEL)
161 class TimerWebScreen(WebScreen):
162 def __init__(self, session,request):
163 WebScreen.__init__(self, session,request)
164 self["TimerList"] = Timer(session,func = Timer.LIST)
165 self["TimerAddEventID"] = Timer(session,func = Timer.ADDBYID)
166 self["TimerAdd"] = Timer(session,func = Timer.ADD)
167 self["TimerDel"] = Timer(session,func = Timer.DEL)
168 self["TimerChange"] = Timer(session,func = Timer.CHANGE)
169 self["TimerListWrite"] = Timer(session,func = Timer.WRITE)
170 self["TVBrowser"] = Timer(session,func = Timer.TVBROWSER)
171 self["RecordNow"] = Timer(session,func = Timer.RECNOW)
173 class RemoteWebScreen(WebScreen):
174 def __init__(self, session,request):
175 WebScreen.__init__(self, session,request)
176 self["RemoteControl"] = RemoteControl(session)
178 class PowerWebScreen(WebScreen):
179 def __init__(self, session,request):
180 WebScreen.__init__(self, session,request)
181 self["PowerState"] = PowerState(session)
183 class ParentControlWebScreen(WebScreen):
184 def __init__(self, session,request):
185 WebScreen.__init__(self, session,request)
186 self["ParentControlList"] = ParentControl(session)
188 class WAPWebScreen(WebScreen):
189 def __init__(self, session,request):
190 WebScreen.__init__(self, session,request)
191 self["WAPFillOptionListSyear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
192 self["WAPFillOptionListSday"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
193 self["WAPFillOptionListSmonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
194 self["WAPFillOptionListShour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
195 self["WAPFillOptionListSmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
197 self["WAPFillOptionListEyear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
198 self["WAPFillOptionListEday"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
199 self["WAPFillOptionListEmonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
200 self["WAPFillOptionListEhour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
201 self["WAPFillOptionListEmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
203 self["WAPFillOptionListRecord"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
204 self["WAPFillOptionListAfterEvent"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
206 self["WAPFillValueName"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
207 self["WAPFillValueDescr"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
209 self["WAPFillOptionListRepeated"] = WAPfunctions(session,func = WAPfunctions.REPEATED)
210 self["WAPServiceList"] = WAPfunctions(session, func = WAPfunctions.SERVICELIST)
212 self["WAPdeleteOldOnSave"] = WAPfunctions(session,func = WAPfunctions.DELETEOLD)
214 class StreamingWebScreen(WebScreen):
215 def __init__(self, session,request):
216 WebScreen.__init__(self, session,request)
217 from Components.Sources.StreamService import StreamService
218 self["StreamService"] = StreamService(self.session.nav)
220 class M3UStreamingWebScreen(WebScreen):
221 def __init__(self, session,request):
222 WebScreen.__init__(self, session,request)
223 from Components.Sources.StaticText import StaticText
224 from Components.Sources.Config import Config
225 from Components.config import config
226 self["ref"] = StaticText()
227 self["localip"] = RequestData(request,what=RequestData.HOST)
229 class TsM3U(WebScreen):
230 def __init__(self, session,request):
231 WebScreen.__init__(self, session,request)
232 from Components.Sources.StaticText import StaticText
233 from Components.Sources.Config import Config
234 from Components.config import config
235 self["file"] = StaticText()
236 self["localip"] = RequestData(request,what=RequestData.HOST)
238 class RestartWebScreen(WebScreen):
239 def __init__(self, session,request):
240 WebScreen.__init__(self, session,request)
242 plugin.restartWebserver()
244 class GetPid(WebScreen):
245 def __init__(self, session,request):
246 WebScreen.__init__(self, session,request)
247 from Components.Sources.StaticText import StaticText
248 from enigma import iServiceInformation
249 pids = self.session.nav.getCurrentService()
251 pidinfo = pids.info()
252 VPID = hex(pidinfo.getInfo(iServiceInformation.sVideoPID))
253 APID = hex(pidinfo.getInfo(iServiceInformation.sAudioPID))
254 PPID = hex(pidinfo.getInfo(iServiceInformation.sPMTPID))
255 self["pids"] = StaticText("%s,%s,%s"%(PPID.lstrip("0x"),VPID.lstrip("0x"),APID.lstrip("0x")))
256 self["localip"] = RequestData(request,what=RequestData.HOST)
259 # implements the 'render'-call.
260 # this will act as a downstream_element, like a renderer.
261 class OneTimeElement(Element):
262 def __init__(self, id):
263 Element.__init__(self)
266 # CHECKME: is this ok performance-wise?
267 def handleCommand(self, args):
268 if self.source_id.find(",") >=0:
269 paramlist = self.source_id.split(",")
271 for key in paramlist:
272 arg = args.get(key, [])
276 list[key] = "".join(arg)
279 self.source.handleCommand(list)
281 for c in args.get(self.source_id, []):
282 self.source.handleCommand(c)
284 def render(self, stream):
285 t = self.source.getHTML(self.source_id)
303 class StreamingElement(OneTimeElement):
304 def __init__(self, id):
305 OneTimeElement.__init__(self, id)
308 def changed(self, what):
310 self.render(self.stream)
312 def setStream(self, stream):
315 # a to-be-filled list item
317 def __init__(self, name, filternum):
319 self.filternum = filternum
321 class TextToHTML(Converter):
322 def __init__(self, arg):
323 Converter.__init__(self, arg)
325 def getHTML(self, id):
326 return self.source.text # encode & etc. here!
328 class TextToURL(Converter):
329 def __init__(self, arg):
330 Converter.__init__(self, arg)
332 def getHTML(self, id):
333 return self.source.text.replace(" ","%20")
335 class ReturnEmptyXML(Converter):
336 def __init__(self, arg):
337 Converter.__init__(self, arg)
339 def getHTML(self, id):
340 return "<rootElement></rootElement>"
342 # a null-output. Useful if you only want to issue a command.
343 class Null(Converter):
344 def __init__(self, arg):
345 Converter.__init__(self, arg)
347 def getHTML(self, id):
350 class JavascriptUpdate(Converter):
351 def __init__(self, arg):
352 Converter.__init__(self, arg)
354 def getHTML(self, id):
355 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
356 # all other will replace this in JS
357 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '°'))
359 # the performant 'listfiller'-engine (plfe)
360 class ListFiller(Converter):
361 def __init__(self, arg):
362 Converter.__init__(self, arg)
363 # print "ListFiller-arg: ",arg
367 lut = self.source.lut
368 conv_args = self.converter_arguments
370 # now build a ["string", 1, "string", 2]-styled list, with indices into the
371 # list to avoid lookup of item name for each entry
372 lutlist = [ isinstance(element, basestring) and (element, None) or (lut[element.name], element.filternum) for element in conv_args ]
374 # now, for the huge list, do:
376 append = strlist.append
378 for (element, filternum) in lutlist:
382 append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
384 append(str(item[element]).replace("&", "&").replace("<", "<").replace('"', '"').replace(">", ">"))
386 append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
388 append(str(item[element]))
389 # (this will be done in c++ later!)
390 return ''.join(strlist)
392 text = property(getText)
394 class webifHandler(ContentHandler):
395 def __init__(self, session,request):
399 self.session = session
401 self.request = request
403 def startElement(self, name, attrs):
404 if name == "e2:screen":
405 self.screen = eval(attrs["name"])(self.session,self.request) # fixme
406 self.screens.append(self.screen)
409 if name[:3] == "e2:":
412 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
416 tag = ''.join(tag)#.encode('utf-8')
420 elif self.mode == 1: # expect "<e2:element>"
421 assert name == "e2:element", "found %s instead of e2:element" % name
422 source = attrs["source"]
423 self.source_id = str(attrs.get("id", source))
424 self.source = self.screen[source]
425 self.is_streaming = "streaming" in attrs
426 elif self.mode == 2: # expect "<e2:convert>"
427 if name[:3] == "e2:":
428 assert name == "e2:convert"
430 ctype = attrs["type"]
432 # TODO: we need something better here
433 if ctype[:4] == "web:": # for now
434 self.converter = eval(ctype[4:])
437 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
439 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
444 assert name == "e2:item", "found %s instead of e2:item!" % name
445 assert "name" in attrs, "e2:item must have a name= attribute!"
446 filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4}[attrs.get("filter", "")]
447 self.sub.append(ListItem(attrs["name"], filter))
449 def endElement(self, name):
450 if name == "e2:screen":
454 tag = "</" + name + ">"
457 elif self.mode == 2 and name[:3] != "e2:":
459 elif self.mode == 2: # closed 'convert' -> sub
460 if len(self.sub) == 1:
461 self.sub = self.sub[0]
462 c = self.converter(self.sub)
463 c.connect(self.source)
466 elif self.mode == 1: # closed 'element'
467 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
468 if not self.is_streaming:
469 c = OneTimeElement(self.source_id)
471 c = StreamingElement(self.source_id)
473 c.connect(self.source)
475 self.screen.renderer.append(c)
478 if name[:3] == "e2:":
481 def processingInstruction(self, target, data):
482 self.res.append('<?' + target + ' ' + data + '>')
484 def characters(self, ch):
485 ch = ch.encode('utf-8')
491 def startEntity(self, name):
492 self.res.append('&' + name + ';');
495 for screen in self.screens:
499 print "screen cleanup!"
500 for screen in self.screens:
505 def renderPage(stream, path, req, session):
507 # read in the template, create required screens
508 # we don't have persistense yet.
509 # if we had, this first part would only be done once.
510 handler = webifHandler(session,req)
511 parser = make_parser()
512 parser.setFeature(feature_namespaces, 0)
513 parser.setContentHandler(handler)
514 parser.parse(open(util.sibpath(__file__, path)))
516 # by default, we have non-streaming pages
519 # first, apply "commands" (aka. URL argument)
520 for x in handler.res:
521 if isinstance(x, Element):
522 x.handleCommand(req.args)
526 # now, we have a list with static texts mixed
527 # with non-static Elements.
528 # flatten this list, write into the stream.
529 for x in handler.res:
530 if isinstance(x, Element):
531 if isinstance(x, StreamingElement):
539 from twisted.internet import reactor
541 reactor.callLater(3, ping, s)
543 # if we met a "StreamingElement", there is at least one
544 # element which wants to output data more than once,
545 # i.e. on host-originated changes.
546 # in this case, don't finish yet, don't cleanup yet,
547 # but instead do that when the client disconnects.
553 # you *need* something which constantly sends something in a regular interval,
554 # in order to detect disconnected clients.
555 # i agree that this "ping" sucks terrible, so better be sure to have something
556 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
558 stream.closed_callback = lambda: handler.cleanup()