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 Screens.ChannelSelection import service_types_tv, service_types_radio
15 from Tools.Import import my_import
17 from Components.Sources.Source import ObsoleteSource
19 from Components.Sources.Clock import Clock
20 from Components.Sources.ServiceList import ServiceList
22 from WebComponents.Sources.ServiceListRecursive import ServiceListRecursive
23 from WebComponents.Sources.Volume import Volume
24 from WebComponents.Sources.EPG import EPG
25 from WebComponents.Sources.Timer import Timer
26 from WebComponents.Sources.Movie import Movie
27 from WebComponents.Sources.Message import Message
28 from WebComponents.Sources.PowerState import PowerState
29 from WebComponents.Sources.RemoteControl import RemoteControl
30 from WebComponents.Sources.Settings import Settings
31 from WebComponents.Sources.SubServices import SubServices
32 from WebComponents.Sources.ParentControl import ParentControl
33 from WebComponents.Sources.About import About
34 from WebComponents.Sources.RequestData import RequestData
35 from WebComponents.Sources.AudioTracks import AudioTracks
36 from WebComponents.Sources.WAPfunctions import WAPfunctions
37 from WebComponents.Sources.MP import MP
38 from WebComponents.Sources.Files import Files
39 from WebComponents.Sources.ServiceListReload import ServiceListReload
40 from WebComponents.Sources.AT import AT
42 from Components.Sources.FrontendStatus import FrontendStatus
44 from Components.Converter.Converter import Converter
46 from Components.Element import Element
48 from xml.sax import make_parser
49 from xml.sax.handler import ContentHandler, feature_namespaces
50 from xml.sax.saxutils import escape as escape_xml
51 from twisted.python import util
53 # prototype of the new web frontend template system.
55 class WebScreen(Screen):
56 def __init__(self, session, request):
57 Screen.__init__(self, session)
58 self.stand_alone = True
59 self.request = request
62 class DummyWebScreen(WebScreen):
63 #use it, if you dont need any source, just to can do a static file with an xml-file
64 def __init__(self, session,request):
65 WebScreen.__init__(self, session,request)
67 class UpdateWebScreen(WebScreen):
68 def __init__(self, session,request):
69 WebScreen.__init__(self, session,request)
70 self["CurrentTime"] = Clock()
71 fav = eServiceReference(service_types_tv + ' FROM BOUQUET "bouquets.tv" ORDER BY bouquet')
73 class MessageWebScreen(WebScreen):
74 def __init__(self, session,request):
75 WebScreen.__init__(self, session,request)
76 self["Message"] = Message(session,func = Message.PRINT)
77 self["GetAnswer"] = Message(session,func = Message.ANSWER)
79 class ServiceListReloadWebScreen(WebScreen):
80 def __init__(self, session,request):
81 WebScreen.__init__(self, session,request)
82 self["ServiceListReload"] = ServiceListReload(session)
84 class AudioWebScreen(WebScreen):
85 def __init__(self, session,request):
86 WebScreen.__init__(self, session,request)
87 self["AudioTracks"] = AudioTracks(session, func=AudioTracks.GET)
88 self["SelectAudioTrack"] = AudioTracks(session, func=AudioTracks.SET)
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)
114 fav = eServiceReference(service_types_tv + ' FROM BOUQUET "bouquets.tv" ORDER BY bouquet')
115 self["SwitchService"] = ServiceList(fav, command_func = self.zapTo, validate_commands=False)
116 self["ServiceList"] = ServiceList(fav, command_func = self.getServiceList, validate_commands=False)
117 self["ServiceListRecursive"] = ServiceListRecursive(session, func=ServiceListRecursive.FETCH)
119 def getServiceList(self, sRef):
120 self["ServiceList"].root = sRef
122 def zapTo(self, reftozap):
123 from Components.config import config
124 pc = config.ParentalControl.configured.value
126 config.ParentalControl.configured.value = False
127 if config.plugins.Webinterface.allowzapping.value:
128 self.session.nav.playService(reftozap)
130 config.ParentalControl.configured.value = pc
132 switching config.ParentalControl.configured.value
133 ugly, but necessary :(
136 class EPGWebScreen(WebScreen):
137 def __init__(self, session,request):
138 WebScreen.__init__(self, session,request)
139 self["EPGTITLE"] = EPG(session,func=EPG.TITLE)
140 self["EPGSERVICE"] = EPG(session,func=EPG.SERVICE)
141 self["EPGNOW"] = EPG(session,func=EPG.NOW)
142 self["EPGNEXT"] = EPG(session,func=EPG.NEXT)
144 class MovieWebScreen(WebScreen):
145 def __init__(self, session,request):
146 WebScreen.__init__(self, session,request)
147 from Components.MovieList import MovieList
148 from Tools.Directories import resolveFilename,SCOPE_HDD
149 movielist = MovieList(eServiceReference("2:0:1:0:0:0:0:0:0:0:" + resolveFilename(SCOPE_HDD)))
150 self["MovieList"] = Movie(session,movielist,func = Movie.LIST)
151 self["MovieFileDel"] = Movie(session,movielist,func = Movie.DEL)
152 self["MovieTags"] = Movie(session,movielist,func = Movie.TAGS)
154 class MediaPlayerWebScreen(WebScreen):
155 def __init__(self, session,request):
156 WebScreen.__init__(self, session,request)
157 self["FileList"] = MP(session,func = MP.LIST)
158 self["PlayFile"] = MP(session,func = MP.PLAY)
159 self["Command"] = MP(session,func = MP.COMMAND)
160 self["WritePlaylist"] = MP(session,func = MP.WRITEPLAYLIST)
162 class AutoTimerWebScreen(WebScreen):
163 def __init__(self, session,request):
164 WebScreen.__init__(self, session,request)
165 self["AutoTimerList"] = AT(session,func = AT.LIST)
166 self["AutoTimerWrite"] = AT(session,func = AT.WRITE)
168 class FilesWebScreen(WebScreen):
169 def __init__(self, session,request):
170 WebScreen.__init__(self, session,request)
171 self["DelFile"] = Files(session,func = Files.DEL)
173 class TimerWebScreen(WebScreen):
174 def __init__(self, session,request):
175 WebScreen.__init__(self, session,request)
176 self["TimerList"] = Timer(session,func = Timer.LIST)
177 self["TimerAddEventID"] = Timer(session,func = Timer.ADDBYID)
178 self["TimerAdd"] = Timer(session,func = Timer.ADD)
179 self["TimerDel"] = Timer(session,func = Timer.DEL)
180 self["TimerChange"] = Timer(session,func = Timer.CHANGE)
181 self["TimerListWrite"] = Timer(session,func = Timer.WRITE)
182 self["TVBrowser"] = Timer(session,func = Timer.TVBROWSER)
183 self["RecordNow"] = Timer(session,func = Timer.RECNOW)
185 class RemoteWebScreen(WebScreen):
186 def __init__(self, session,request):
187 WebScreen.__init__(self, session,request)
188 self["RemoteControl"] = RemoteControl(session)
190 class PowerWebScreen(WebScreen):
191 def __init__(self, session,request):
192 WebScreen.__init__(self, session,request)
193 self["PowerState"] = PowerState(session)
195 class ParentControlWebScreen(WebScreen):
196 def __init__(self, session,request):
197 WebScreen.__init__(self, session,request)
198 self["ParentControlList"] = ParentControl(session)
200 class WAPWebScreen(WebScreen):
201 def __init__(self, session,request):
202 WebScreen.__init__(self, session,request)
203 self["WAPFillOptionListSyear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
204 self["WAPFillOptionListSday"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
205 self["WAPFillOptionListSmonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
206 self["WAPFillOptionListShour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
207 self["WAPFillOptionListSmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
209 self["WAPFillOptionListEyear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
210 self["WAPFillOptionListEday"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
211 self["WAPFillOptionListEmonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
212 self["WAPFillOptionListEhour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
213 self["WAPFillOptionListEmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
215 self["WAPFillOptionListRecord"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
216 self["WAPFillOptionListAfterEvent"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
218 self["WAPFillValueName"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
219 self["WAPFillValueDescr"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
221 self["WAPFillOptionListRepeated"] = WAPfunctions(session,func = WAPfunctions.REPEATED)
222 self["WAPServiceList"] = WAPfunctions(session, func = WAPfunctions.SERVICELIST)
224 self["WAPdeleteOldOnSave"] = WAPfunctions(session,func = WAPfunctions.DELETEOLD)
226 class StreamingWebScreen(WebScreen):
227 def __init__(self, session,request):
228 WebScreen.__init__(self, session,request)
229 from Components.Sources.StreamService import StreamService
230 self["StreamService"] = StreamService(self.session.nav)
232 class M3UStreamingWebScreen(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["ref"] = StaticText()
239 self["localip"] = RequestData(request,what=RequestData.HOST)
241 class TsM3U(WebScreen):
242 def __init__(self, session,request):
243 WebScreen.__init__(self, session,request)
244 from Components.Sources.StaticText import StaticText
245 from Components.Sources.Config import Config
246 from Components.config import config
247 self["file"] = StaticText()
248 self["localip"] = RequestData(request,what=RequestData.HOST)
250 class RestartWebScreen(WebScreen):
251 def __init__(self, session,request):
252 WebScreen.__init__(self, session,request)
254 plugin.restartWebserver()
256 class GetPid(WebScreen):
257 def __init__(self, session,request):
258 WebScreen.__init__(self, session,request)
259 from Components.Sources.StaticText import StaticText
260 from enigma import iServiceInformation
261 pids = self.session.nav.getCurrentService()
263 pidinfo = pids.info()
264 VPID = hex(pidinfo.getInfo(iServiceInformation.sVideoPID))
265 APID = hex(pidinfo.getInfo(iServiceInformation.sAudioPID))
266 PPID = hex(pidinfo.getInfo(iServiceInformation.sPMTPID))
267 self["pids"] = StaticText("%s,%s,%s"%(PPID.lstrip("0x"),VPID.lstrip("0x"),APID.lstrip("0x")))
268 self["localip"] = RequestData(request,what=RequestData.HOST)
271 # implements the 'render'-call.
272 # this will act as a downstream_element, like a renderer.
273 class OneTimeElement(Element):
274 def __init__(self, id):
275 Element.__init__(self)
278 # CHECKME: is this ok performance-wise?
279 def handleCommand(self, args):
280 if self.source_id.find(",") >=0:
281 paramlist = self.source_id.split(",")
283 for key in paramlist:
284 arg = args.get(key, [])
288 list[key] = "".join(arg)
291 self.source.handleCommand(list)
293 for c in args.get(self.source_id, []):
294 self.source.handleCommand(c)
296 def render(self, stream):
297 t = self.source.getHTML(self.source_id)
315 class MacroElement(OneTimeElement):
316 def __init__(self, id, macro_dict, macro_name):
317 OneTimeElement.__init__(self, id)
318 self.macro_dict = macro_dict
319 self.macro_name = macro_name
321 def render(self, stream):
322 self.macro_dict[self.macro_name] = self.source.getHTML(self.source_id)
324 class StreamingElement(OneTimeElement):
325 def __init__(self, id):
326 OneTimeElement.__init__(self, id)
329 def changed(self, what):
331 self.render(self.stream)
333 def setStream(self, stream):
336 # a to-be-filled list item
338 def __init__(self, name, filternum):
340 self.filternum = filternum
343 def __init__(self, macrodict, macroname):
344 self.macrodict = macrodict
345 self.macroname = macroname
347 class TextToHTML(Converter):
348 def __init__(self, arg):
349 Converter.__init__(self, arg)
351 def getHTML(self, id):
352 return self.source.text # encode & etc. here!
354 class TextToXML(Converter):
355 def __init__(self, arg):
356 Converter.__init__(self, arg)
358 def getHTML(self, id):
359 return escape_xml(self.source.text).replace("\x19", "").replace("\x1c", "").replace("\x1e", "")
361 class TextToURL(Converter):
362 def __init__(self, arg):
363 Converter.__init__(self, arg)
365 def getHTML(self, id):
366 return self.source.text.replace(" ","%20")
368 class ReturnEmptyXML(Converter):
369 def __init__(self, arg):
370 Converter.__init__(self, arg)
372 def getHTML(self, id):
373 return "<rootElement></rootElement>"
375 # a null-output. Useful if you only want to issue a command.
376 class Null(Converter):
377 def __init__(self, arg):
378 Converter.__init__(self, arg)
380 def getHTML(self, id):
383 class JavascriptUpdate(Converter):
384 def __init__(self, arg):
385 Converter.__init__(self, arg)
387 def getHTML(self, id):
388 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
389 # all other will replace this in JS
390 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '°'))
392 # the performant 'listfiller'-engine (plfe)
393 class ListFiller(Converter):
394 def __init__(self, arg):
395 Converter.__init__(self, arg)
396 # print "ListFiller-arg: ",arg
400 lut = self.source.lut
401 conv_args = self.converter_arguments
403 # now build a ["string", 1, "string", 2]-styled list, with indices into the
404 # list to avoid lookup of item name for each entry
406 for element in conv_args:
407 if isinstance(element, basestring):
408 lutlist.append((element, None))
409 elif isinstance(element, ListItem):
410 lutlist.append((lut[element.name], element.filternum))
411 elif isinstance(element, ListMacroItem):
412 lutlist.append((element.macrodict[element.macroname], None))
414 raise "neither string, ListItem nor ListMacroItem"
416 # now, for the huge list, do:
418 append = strlist.append
420 for (element, filternum) in lutlist:
424 append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
426 #append(str(item[element]).replace("&", "&").replace("<", "<").replace('"', '"').replace(">", ">"))
427 append(escape_xml(str(item[element])))
429 append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
431 append(str(item[element]))
432 # (this will be done in c++ later!)
435 return ''.join(strlist)
437 text = property(getText)
439 class webifHandler(ContentHandler):
440 def __init__(self, session, request):
444 self.session = session
446 self.request = request
449 def start_element(self, attrs):
452 wsource = attrs["source"]
454 path = wsource.split('.')
456 scr = self.screen.getRelatedScreen(path[0])
458 print "[webif.py] Parent Screen not found!"
462 source = scr.get(path[0])
464 if isinstance(source, ObsoleteSource):
465 # however, if we found an "obsolete source", issue warning, and resolve the real source.
466 print "WARNING: WEBIF '%s' USES OBSOLETE SOURCE '%s', USE '%s' INSTEAD!" % (name, wsource, source.new_source)
467 print "OBSOLETE SOURCE WILL BE REMOVED %s, PLEASE UPDATE!" % (source.removal_date)
468 if source.description:
469 print source.description
471 wsource = source.new_source
474 # otherwise, use that source.
477 self.source_id = str(attrs.get("id", wsource))
478 self.is_streaming = "streaming" in attrs
479 self.macro_name = attrs.get("macro") or None
481 def end_element(self):
482 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
483 if not self.is_streaming:
484 if self.macro_name is None:
485 c = OneTimeElement(self.source_id)
487 c = MacroElement(self.source_id, self.macros, self.macro_name)
489 assert self.macro_name is None
490 c = StreamingElement(self.source_id)
492 c.connect(self.source)
494 self.screen.renderer.append(c)
497 def start_convert(self, attrs):
498 ctype = attrs["type"]
500 # TODO: we need something better here
501 if ctype[:4] == "web:": # for now
502 self.converter = eval(ctype[4:])
505 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
507 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
510 def end_convert(self):
511 if len(self.sub) == 1:
512 self.sub = self.sub[0]
513 c = self.converter(self.sub)
514 c.connect(self.source)
518 def parse_item(self, attrs):
520 filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4}[attrs.get("filter", "")]
521 self.sub.append(ListItem(attrs["name"], filter))
523 assert "macro" in attrs, "e2:item must have a name= or macro= attribute!"
524 self.sub.append(ListMacroItem(self.macros, attrs["macro"]))
526 def startElement(self, name, attrs):
527 if name == "e2:screen":
528 self.screen = eval(attrs["name"])(self.session,self.request) # fixme
529 self.screens.append(self.screen)
532 if name[:3] == "e2:":
535 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
539 tag = ''.join(tag)#.encode('utf-8')
543 elif self.mode == 1: # expect "<e2:element>"
544 assert name == "e2:element", "found %s instead of e2:element" % name
545 self.start_element(attrs)
546 elif self.mode == 2: # expect "<e2:convert>"
547 if name[:3] == "e2:":
548 assert name == "e2:convert"
549 self.start_convert(attrs)
553 assert name == "e2:item", "found %s instead of e2:item!" % name
555 self.parse_item(attrs)
557 def endElement(self, name):
558 if name == "e2:screen":
562 tag = "</" + name + ">"
565 elif self.mode == 2 and name[:3] != "e2:":
567 elif self.mode == 2: # closed 'convert' -> sub
569 elif self.mode == 1: # closed 'element'
571 if name[:3] == "e2:":
574 def processingInstruction(self, target, data):
575 self.res.append('<?' + target + ' ' + data + '>')
577 def characters(self, ch):
578 ch = ch.encode('utf-8')
584 def startEntity(self, name):
585 self.res.append('&' + name + ';');
588 for screen in self.screens:
592 print "screen cleanup!"
593 for screen in self.screens:
598 def renderPage(stream, path, req, session):
600 # read in the template, create required screens
601 # we don't have persistense yet.
602 # if we had, this first part would only be done once.
603 handler = webifHandler(session,req)
604 parser = make_parser()
605 parser.setFeature(feature_namespaces, 0)
606 parser.setContentHandler(handler)
607 parser.parse(open(util.sibpath(__file__, path)))
609 # by default, we have non-streaming pages
612 # first, apply "commands" (aka. URL argument)
613 for x in handler.res:
614 if isinstance(x, Element):
615 x.handleCommand(req.args)
619 # now, we have a list with static texts mixed
620 # with non-static Elements.
621 # flatten this list, write into the stream.
622 for x in handler.res:
623 if isinstance(x, Element):
624 if isinstance(x, StreamingElement):
632 from twisted.internet import reactor
634 reactor.callLater(3, ping, s)
636 # if we met a "StreamingElement", there is at least one
637 # element which wants to output data more than once,
638 # i.e. on host-originated changes.
639 # in this case, don't finish yet, don't cleanup yet,
640 # but instead do that when the client disconnects.
642 streamFinish(handler, stream)
645 # you *need* something which constantly sends something in a regular interval,
646 # in order to detect disconnected clients.
647 # i agree that this "ping" sucks terrible, so better be sure to have something
648 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
650 stream.closed_callback = lambda : streamFinish(handler, stream)
652 def streamFinish(handler, stream):