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.Source import Source, ObsoleteSource
20 from Components.Sources.Clock import Clock
21 from Components.Sources.ServiceList import ServiceList
23 from WebComponents.Sources.ServiceListRecursive import ServiceListRecursive
24 from WebComponents.Sources.Volume import Volume
25 from WebComponents.Sources.EPG import EPG
26 from WebComponents.Sources.Timer import Timer
27 from WebComponents.Sources.Movie import Movie
28 from WebComponents.Sources.Message import Message
29 from WebComponents.Sources.PowerState import PowerState
30 from WebComponents.Sources.RemoteControl import RemoteControl
31 from WebComponents.Sources.Settings import Settings
32 from WebComponents.Sources.SubServices import SubServices
33 from WebComponents.Sources.ParentControl import ParentControl
34 from WebComponents.Sources.About import About
35 from WebComponents.Sources.RequestData import RequestData
36 from WebComponents.Sources.AudioTracks import AudioTracks
37 from WebComponents.Sources.WAPfunctions import WAPfunctions
38 from WebComponents.Sources.MP import MP
39 from WebComponents.Sources.Files import Files
41 from Components.Sources.FrontendStatus import FrontendStatus
43 from Components.Converter.Converter import Converter
45 from Components.Element import Element
47 from xml.sax import make_parser
48 from xml.sax.handler import ContentHandler, feature_namespaces
50 from twisted.python import util
52 # prototype of the new web frontend template system.
54 class WebScreen(Screen):
55 def __init__(self, session, request):
56 Screen.__init__(self, session)
57 self.stand_alone = True
58 self.request = request
61 class DummyWebScreen(WebScreen):
62 #use it, if you dont need any source, just to can do a static file with an xml-file
63 def __init__(self, session,request):
64 WebScreen.__init__(self, session,request)
66 class UpdateWebScreen(InfoBarServiceName, InfoBarEvent,InfoBarTuner,WebScreen):
67 def __init__(self, session,request):
68 WebScreen.__init__(self, session,request)
69 InfoBarServiceName.__init__(self)
70 InfoBarEvent.__init__(self)
71 InfoBarTuner.__init__(self)
72 self["CurrentTime"] = Clock()
73 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')
79 class MessageWebScreen(WebScreen):
80 def __init__(self, session,request):
81 WebScreen.__init__(self, session,request)
82 self["Message"] = Message(session,func = Message.PRINT)
83 self["GetAnswer"] = Message(session,func = Message.ANSWER)
85 class AudioWebScreen(WebScreen):
86 def __init__(self, session,request):
87 WebScreen.__init__(self, session,request)
88 self["AudioTracks"] = AudioTracks(session)
90 class AboutWebScreen(WebScreen):
91 def __init__(self, session,request):
92 WebScreen.__init__(self, session,request)
93 self["About"] = About(session)
95 class VolumeWebScreen(WebScreen):
96 def __init__(self, session,request):
97 WebScreen.__init__(self, session,request)
98 self["Volume"] = Volume(session)
100 class SettingsWebScreen(WebScreen):
101 def __init__(self, session,request):
102 WebScreen.__init__(self, session,request)
103 self["Settings"] = Settings(session)
105 class SubServiceWebScreen(WebScreen):
106 def __init__(self, session,request):
107 WebScreen.__init__(self, session,request)
108 self["SubServices"] = SubServices(session)
110 class ServiceWebScreen(WebScreen):
111 def __init__(self, session,request):
112 WebScreen.__init__(self, session,request)
113 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')
114 self["SwitchService"] = ServiceList(fav, command_func = self.zapTo, validate_commands=False)
115 self["ServiceList"] = ServiceList(fav, command_func = self.getServiceList, validate_commands=False)
116 self["ServiceListRecursive"] = ServiceListRecursive(session, func=ServiceListRecursive.FETCH)
118 def getServiceList(self, sRef):
119 self["ServiceList"].root = sRef
121 def zapTo(self, reftozap):
122 from Components.config import config
123 pc = config.ParentalControl.configured.value
125 config.ParentalControl.configured.value = False
126 self.session.nav.playService(reftozap)
128 config.ParentalControl.configured.value = pc
130 switching config.ParentalControl.configured.value
131 ugly, but necessary :(
134 class EPGWebScreen(WebScreen):
135 def __init__(self, session,request):
136 WebScreen.__init__(self, session,request)
137 self["EPGTITLE"] = EPG(session,func=EPG.TITLE)
138 self["EPGSERVICE"] = EPG(session,func=EPG.SERVICE)
139 self["EPGNOW"] = EPG(session,func=EPG.NOW)
141 class MovieWebScreen(WebScreen):
142 def __init__(self, session,request):
143 WebScreen.__init__(self, session,request)
144 from Components.MovieList import MovieList
145 from Tools.Directories import resolveFilename,SCOPE_HDD
146 movielist = MovieList(eServiceReference("2:0:1:0:0:0:0:0:0:0:" + resolveFilename(SCOPE_HDD)))
147 self["MovieList"] = Movie(session,movielist,func = Movie.LIST)
148 self["MovieFileDel"] = Movie(session,movielist,func = Movie.DEL)
149 self["MovieTags"] = Movie(session,movielist,func = Movie.TAGS)
151 class MediaPlayerWebScreen(WebScreen):
152 def __init__(self, session,request):
153 WebScreen.__init__(self, session,request)
154 self["FileList"] = MP(session,func = MP.LIST)
155 self["PlayFile"] = MP(session,func = MP.PLAY)
156 self["Command"] = MP(session,func = MP.COMMAND)
157 self["WritePlaylist"] = MP(session,func = MP.WRITEPLAYLIST)
159 class FilesWebScreen(WebScreen):
160 def __init__(self, session,request):
161 WebScreen.__init__(self, session,request)
162 self["DelFile"] = Files(session,func = Files.DEL)
164 class TimerWebScreen(WebScreen):
165 def __init__(self, session,request):
166 WebScreen.__init__(self, session,request)
167 self["TimerList"] = Timer(session,func = Timer.LIST)
168 self["TimerAddEventID"] = Timer(session,func = Timer.ADDBYID)
169 self["TimerAdd"] = Timer(session,func = Timer.ADD)
170 self["TimerDel"] = Timer(session,func = Timer.DEL)
171 self["TimerChange"] = Timer(session,func = Timer.CHANGE)
172 self["TimerListWrite"] = Timer(session,func = Timer.WRITE)
173 self["TVBrowser"] = Timer(session,func = Timer.TVBROWSER)
174 self["RecordNow"] = Timer(session,func = Timer.RECNOW)
176 class RemoteWebScreen(WebScreen):
177 def __init__(self, session,request):
178 WebScreen.__init__(self, session,request)
179 self["RemoteControl"] = RemoteControl(session)
181 class PowerWebScreen(WebScreen):
182 def __init__(self, session,request):
183 WebScreen.__init__(self, session,request)
184 self["PowerState"] = PowerState(session)
186 class ParentControlWebScreen(WebScreen):
187 def __init__(self, session,request):
188 WebScreen.__init__(self, session,request)
189 self["ParentControlList"] = ParentControl(session)
191 class WAPWebScreen(WebScreen):
192 def __init__(self, session,request):
193 WebScreen.__init__(self, session,request)
194 self["WAPFillOptionListSyear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
195 self["WAPFillOptionListSday"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
196 self["WAPFillOptionListSmonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
197 self["WAPFillOptionListShour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
198 self["WAPFillOptionListSmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
200 self["WAPFillOptionListEyear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
201 self["WAPFillOptionListEday"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
202 self["WAPFillOptionListEmonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
203 self["WAPFillOptionListEhour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
204 self["WAPFillOptionListEmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
206 self["WAPFillOptionListRecord"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
207 self["WAPFillOptionListAfterEvent"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
209 self["WAPFillValueName"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
210 self["WAPFillValueDescr"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
212 self["WAPFillOptionListRepeated"] = WAPfunctions(session,func = WAPfunctions.REPEATED)
213 self["WAPServiceList"] = WAPfunctions(session, func = WAPfunctions.SERVICELIST)
215 self["WAPdeleteOldOnSave"] = WAPfunctions(session,func = WAPfunctions.DELETEOLD)
217 class StreamingWebScreen(WebScreen):
218 def __init__(self, session,request):
219 WebScreen.__init__(self, session,request)
220 from Components.Sources.StreamService import StreamService
221 self["StreamService"] = StreamService(self.session.nav)
223 class M3UStreamingWebScreen(WebScreen):
224 def __init__(self, session,request):
225 WebScreen.__init__(self, session,request)
226 from Components.Sources.StaticText import StaticText
227 from Components.Sources.Config import Config
228 from Components.config import config
229 self["ref"] = StaticText()
230 self["localip"] = RequestData(request,what=RequestData.HOST)
232 class TsM3U(WebScreen):
233 def __init__(self, session,request):
234 WebScreen.__init__(self, session,request)
235 from Components.Sources.StaticText import StaticText
236 from Components.Sources.Config import Config
237 from Components.config import config
238 self["file"] = StaticText()
239 self["localip"] = RequestData(request,what=RequestData.HOST)
241 class RestartWebScreen(WebScreen):
242 def __init__(self, session,request):
243 WebScreen.__init__(self, session,request)
245 plugin.restartWebserver()
247 class GetPid(WebScreen):
248 def __init__(self, session,request):
249 WebScreen.__init__(self, session,request)
250 from Components.Sources.StaticText import StaticText
251 from enigma import iServiceInformation
252 pids = self.session.nav.getCurrentService()
254 pidinfo = pids.info()
255 VPID = hex(pidinfo.getInfo(iServiceInformation.sVideoPID))
256 APID = hex(pidinfo.getInfo(iServiceInformation.sAudioPID))
257 PPID = hex(pidinfo.getInfo(iServiceInformation.sPMTPID))
258 self["pids"] = StaticText("%s,%s,%s"%(PPID.lstrip("0x"),VPID.lstrip("0x"),APID.lstrip("0x")))
259 self["localip"] = RequestData(request,what=RequestData.HOST)
262 # implements the 'render'-call.
263 # this will act as a downstream_element, like a renderer.
264 class OneTimeElement(Element):
265 def __init__(self, id):
266 Element.__init__(self)
269 # CHECKME: is this ok performance-wise?
270 def handleCommand(self, args):
271 if self.source_id.find(",") >=0:
272 paramlist = self.source_id.split(",")
274 for key in paramlist:
275 arg = args.get(key, [])
279 list[key] = "".join(arg)
282 self.source.handleCommand(list)
284 for c in args.get(self.source_id, []):
285 self.source.handleCommand(c)
287 def render(self, stream):
288 t = self.source.getHTML(self.source_id)
306 class StreamingElement(OneTimeElement):
307 def __init__(self, id):
308 OneTimeElement.__init__(self, id)
311 def changed(self, what):
313 self.render(self.stream)
315 def setStream(self, stream):
318 # a to-be-filled list item
320 def __init__(self, name, filternum):
322 self.filternum = filternum
324 class TextToHTML(Converter):
325 def __init__(self, arg):
326 Converter.__init__(self, arg)
328 def getHTML(self, id):
329 return self.source.text # encode & etc. here!
331 class TextToURL(Converter):
332 def __init__(self, arg):
333 Converter.__init__(self, arg)
335 def getHTML(self, id):
336 return self.source.text.replace(" ","%20")
338 class ReturnEmptyXML(Converter):
339 def __init__(self, arg):
340 Converter.__init__(self, arg)
342 def getHTML(self, id):
343 return "<rootElement></rootElement>"
345 # a null-output. Useful if you only want to issue a command.
346 class Null(Converter):
347 def __init__(self, arg):
348 Converter.__init__(self, arg)
350 def getHTML(self, id):
353 class JavascriptUpdate(Converter):
354 def __init__(self, arg):
355 Converter.__init__(self, arg)
357 def getHTML(self, id):
358 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
359 # all other will replace this in JS
360 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '°'))
362 # the performant 'listfiller'-engine (plfe)
363 class ListFiller(Converter):
364 def __init__(self, arg):
365 Converter.__init__(self, arg)
366 # print "ListFiller-arg: ",arg
370 lut = self.source.lut
371 conv_args = self.converter_arguments
373 # now build a ["string", 1, "string", 2]-styled list, with indices into the
374 # list to avoid lookup of item name for each entry
375 lutlist = [ isinstance(element, basestring) and (element, None) or (lut[element.name], element.filternum) for element in conv_args ]
377 # now, for the huge list, do:
379 append = strlist.append
381 for (element, filternum) in lutlist:
385 append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
387 append(str(item[element]).replace("&", "&").replace("<", "<").replace('"', '"').replace(">", ">"))
389 append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
391 append(str(item[element]))
392 # (this will be done in c++ later!)
393 return ''.join(strlist)
395 text = property(getText)
397 class webifHandler(ContentHandler):
398 def __init__(self, session, request):
402 self.session = session
404 self.request = request
406 def startElement(self, name, attrs):
407 if name == "e2:screen":
408 self.screen = eval(attrs["name"])(self.session,self.request) # fixme
409 self.screens.append(self.screen)
412 if name[:3] == "e2:":
415 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
419 tag = ''.join(tag)#.encode('utf-8')
423 elif self.mode == 1: # expect "<e2:element>"
426 assert name == "e2:element", "found %s instead of e2:element" % name
427 wsource = attrs["source"]
429 path = wsource.split('.')
431 scr = self.screen.getRelatedScreen(path[0])
433 print "[webif.py] Parent Screen not found!"
437 source = scr.get(path[0])
439 if isinstance(source, ObsoleteSource):
440 # however, if we found an "obsolete source", issue warning, and resolve the real source.
441 print "WARNING: WEBIF '%s' USES OBSOLETE SOURCE '%s', USE '%s' INSTEAD!" % (name, wsource, source.new_source)
442 print "OBSOLETE SOURCE WILL BE REMOVED %s, PLEASE UPDATE!" % (source.removal_date)
443 if source.description:
444 print source.description
446 wsource = source.new_source
449 # otherwise, use that source.
452 self.source_id = str(attrs.get("id", wsource))
453 self.is_streaming = "streaming" in attrs
455 elif self.mode == 2: # expect "<e2:convert>"
456 if name[:3] == "e2:":
457 assert name == "e2:convert"
459 ctype = attrs["type"]
461 # TODO: we need something better here
462 if ctype[:4] == "web:": # for now
463 self.converter = eval(ctype[4:])
466 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
468 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
473 assert name == "e2:item", "found %s instead of e2:item!" % name
474 assert "name" in attrs, "e2:item must have a name= attribute!"
475 filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4}[attrs.get("filter", "")]
476 self.sub.append(ListItem(attrs["name"], filter))
478 def endElement(self, name):
479 if name == "e2:screen":
483 tag = "</" + name + ">"
486 elif self.mode == 2 and name[:3] != "e2:":
488 elif self.mode == 2: # closed 'convert' -> sub
489 if len(self.sub) == 1:
490 self.sub = self.sub[0]
491 c = self.converter(self.sub)
492 c.connect(self.source)
495 elif self.mode == 1: # closed 'element'
496 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
497 if not self.is_streaming:
498 c = OneTimeElement(self.source_id)
500 c = StreamingElement(self.source_id)
502 c.connect(self.source)
504 self.screen.renderer.append(c)
507 if name[:3] == "e2:":
510 def processingInstruction(self, target, data):
511 self.res.append('<?' + target + ' ' + data + '>')
513 def characters(self, ch):
514 ch = ch.encode('utf-8')
520 def startEntity(self, name):
521 self.res.append('&' + name + ';');
524 for screen in self.screens:
528 print "screen cleanup!"
529 for screen in self.screens:
534 def renderPage(stream, path, req, session):
536 # read in the template, create required screens
537 # we don't have persistense yet.
538 # if we had, this first part would only be done once.
539 handler = webifHandler(session,req)
540 parser = make_parser()
541 parser.setFeature(feature_namespaces, 0)
542 parser.setContentHandler(handler)
543 parser.parse(open(util.sibpath(__file__, path)))
545 # by default, we have non-streaming pages
548 # first, apply "commands" (aka. URL argument)
549 for x in handler.res:
550 if isinstance(x, Element):
551 x.handleCommand(req.args)
555 # now, we have a list with static texts mixed
556 # with non-static Elements.
557 # flatten this list, write into the stream.
558 for x in handler.res:
559 if isinstance(x, Element):
560 if isinstance(x, StreamingElement):
568 from twisted.internet import reactor
570 reactor.callLater(3, ping, s)
572 # if we met a "StreamingElement", there is at least one
573 # element which wants to output data more than once,
574 # i.e. on host-originated changes.
575 # in this case, don't finish yet, don't cleanup yet,
576 # but instead do that when the client disconnects.
582 # you *need* something which constantly sends something in a regular interval,
583 # in order to detect disconnected clients.
584 # i agree that this "ping" sucks terrible, so better be sure to have something
585 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
587 stream.closed_callback = lambda: handler.cleanup()