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.ServiceListReload import ServiceListReload
39 from WebComponents.Sources.AT import AT
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
49 from xml.sax.saxutils import escape as escape_xml
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(WebScreen):
67 def __init__(self, session, request):
68 WebScreen.__init__(self, session, request)
69 self["CurrentTime"] = Clock()
70 fav = eServiceReference(service_types_tv + ' FROM BOUQUET "bouquets.tv" ORDER BY bouquet')
72 class MessageWebScreen(WebScreen):
73 def __init__(self, session, request):
74 WebScreen.__init__(self, session, request)
75 self["Message"] = Message(session,func = Message.PRINT)
76 self["GetAnswer"] = Message(session,func = Message.ANSWER)
78 class ServiceListReloadWebScreen(WebScreen):
79 def __init__(self, session, request):
80 WebScreen.__init__(self, session, request)
81 self["ServiceListReload"] = ServiceListReload(session)
83 class AudioWebScreen(WebScreen):
84 def __init__(self, session, request):
85 WebScreen.__init__(self, session, request)
86 self["AudioTracks"] = AudioTracks(session, func=AudioTracks.GET)
87 self["SelectAudioTrack"] = AudioTracks(session, func=AudioTracks.SET)
89 class AboutWebScreen(WebScreen):
90 def __init__(self, session, request):
91 WebScreen.__init__(self, session, request)
92 self["About"] = About(session)
94 class VolumeWebScreen(WebScreen):
95 def __init__(self, session, request):
96 WebScreen.__init__(self, session, request)
97 self["Volume"] = Volume(session)
99 class SettingsWebScreen(WebScreen):
100 def __init__(self, session, request):
101 WebScreen.__init__(self, session, request)
102 self["Settings"] = Settings(session)
104 class SubServiceWebScreen(WebScreen):
105 def __init__(self, session, request):
106 WebScreen.__init__(self, session, request)
107 self["SubServices"] = SubServices(session)
109 class ServiceWebScreen(WebScreen):
110 def __init__(self, session, request):
111 WebScreen.__init__(self, session, request)
113 fav = eServiceReference(service_types_tv + ' 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 if config.plugins.Webinterface.allowzapping.value:
127 self.session.nav.playService(reftozap)
129 config.ParentalControl.configured.value = pc
131 switching config.ParentalControl.configured.value
132 ugly, but necessary :(
135 class EPGWebScreen(WebScreen):
136 def __init__(self, session, request):
137 WebScreen.__init__(self, session, request)
138 self["EPGTITLE"] = EPG(session,func=EPG.TITLE)
139 self["EPGSERVICE"] = EPG(session,func=EPG.SERVICE)
140 self["EPGNOW"] = EPG(session,func=EPG.NOW)
141 self["EPGNEXT"] = EPG(session,func=EPG.NEXT)
143 class MovieWebScreen(WebScreen):
144 def __init__(self, session, request):
145 WebScreen.__init__(self, session, request)
146 from Components.MovieList import MovieList
147 from Tools.Directories import resolveFilename,SCOPE_HDD
148 movielist = MovieList(eServiceReference("2:0:1:0:0:0:0:0:0:0:" + resolveFilename(SCOPE_HDD)))
149 self["MovieList"] = Movie(session,movielist,func = Movie.LIST)
150 self["MovieFileDel"] = Movie(session,movielist,func = Movie.DEL)
151 self["MovieTags"] = Movie(session,movielist,func = Movie.TAGS)
153 class MediaPlayerWebScreen(WebScreen):
154 def __init__(self, session, request):
155 WebScreen.__init__(self, session, request)
156 self["FileList"] = MP(session,func = MP.LIST)
157 self["PlayFile"] = MP(session,func = MP.PLAY)
158 self["Command"] = MP(session,func = MP.COMMAND)
159 self["WritePlaylist"] = MP(session,func = MP.WRITEPLAYLIST)
161 class AutoTimerWebScreen(WebScreen):
162 def __init__(self, session, request):
163 WebScreen.__init__(self, session, request)
164 self["AutoTimerList"] = AT(session,func = AT.LIST)
165 self["AutoTimerWrite"] = AT(session,func = AT.WRITE)
167 class TimerWebScreen(WebScreen):
168 def __init__(self, session, request):
169 WebScreen.__init__(self, session, request)
170 self["TimerList"] = Timer(session,func = Timer.LIST)
171 self["TimerAddEventID"] = Timer(session,func = Timer.ADDBYID)
172 self["TimerAdd"] = Timer(session,func = Timer.ADD)
173 self["TimerDel"] = Timer(session,func = Timer.DEL)
174 self["TimerChange"] = Timer(session,func = Timer.CHANGE)
175 self["TimerListWrite"] = Timer(session,func = Timer.WRITE)
176 self["TVBrowser"] = Timer(session,func = Timer.TVBROWSER)
177 self["RecordNow"] = Timer(session,func = Timer.RECNOW)
179 class RemoteWebScreen(WebScreen):
180 def __init__(self, session, request):
181 WebScreen.__init__(self, session, request)
182 self["RemoteControl"] = RemoteControl(session)
184 class PowerWebScreen(WebScreen):
185 def __init__(self, session, request):
186 WebScreen.__init__(self, session, request)
187 self["PowerState"] = PowerState(session)
189 class ParentControlWebScreen(WebScreen):
190 def __init__(self, session, request):
191 WebScreen.__init__(self, session, request)
192 self["ParentControlList"] = ParentControl(session)
194 class WAPWebScreen(WebScreen):
195 def __init__(self, session, request):
196 WebScreen.__init__(self, session, request)
197 self["WAPFillOptionListSyear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
198 self["WAPFillOptionListSday"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
199 self["WAPFillOptionListSmonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
200 self["WAPFillOptionListShour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
201 self["WAPFillOptionListSmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
203 self["WAPFillOptionListEyear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
204 self["WAPFillOptionListEday"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
205 self["WAPFillOptionListEmonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
206 self["WAPFillOptionListEhour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
207 self["WAPFillOptionListEmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
209 self["WAPFillOptionListRecord"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
210 self["WAPFillOptionListAfterEvent"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
212 self["WAPFillValueName"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
213 self["WAPFillValueDescr"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
215 self["WAPFillOptionListRepeated"] = WAPfunctions(session,func = WAPfunctions.REPEATED)
216 self["WAPServiceList"] = WAPfunctions(session, func = WAPfunctions.SERVICELIST)
218 self["WAPdeleteOldOnSave"] = WAPfunctions(session,func = WAPfunctions.DELETEOLD)
220 class StreamingWebScreen(WebScreen):
221 def __init__(self, session, request):
222 WebScreen.__init__(self, session, request)
223 from Components.Sources.StreamService import StreamService
224 self["StreamService"] = StreamService(self.session.nav)
226 class M3UStreamingWebScreen(WebScreen):
227 def __init__(self, session, request):
228 WebScreen.__init__(self, session, request)
229 from Components.Sources.StaticText import StaticText
230 from Components.Sources.Config import Config
231 from Components.config import config
232 self["ref"] = StaticText()
233 self["localip"] = RequestData(request,what=RequestData.HOST)
235 class TsM3U(WebScreen):
236 def __init__(self, session, request):
237 WebScreen.__init__(self, session, request)
238 from Components.Sources.StaticText import StaticText
239 from Components.Sources.Config import Config
240 from Components.config import config
241 self["file"] = StaticText()
242 self["localip"] = RequestData(request,what=RequestData.HOST)
244 class RestartWebScreen(WebScreen):
245 def __init__(self, session, request):
246 WebScreen.__init__(self, session, request)
248 plugin.restartWebserver(session)
250 class GetPid(WebScreen):
251 def __init__(self, session, request):
252 WebScreen.__init__(self, session, request)
253 from Components.Sources.StaticText import StaticText
254 from enigma import iServiceInformation
255 pids = self.session.nav.getCurrentService()
257 pidinfo = pids.info()
258 VPID = hex(pidinfo.getInfo(iServiceInformation.sVideoPID))
259 APID = hex(pidinfo.getInfo(iServiceInformation.sAudioPID))
260 PPID = hex(pidinfo.getInfo(iServiceInformation.sPMTPID))
261 self["pids"] = StaticText("%s,%s,%s"%(PPID.lstrip("0x"),VPID.lstrip("0x"),APID.lstrip("0x")))
263 self["pids"] = StaticText("0x,0x,0x")
265 self["localip"] = RequestData(request,what=RequestData.HOST)
268 # implements the 'render'-call.
269 # this will act as a downstream_element, like a renderer.
270 class OneTimeElement(Element):
271 def __init__(self, id):
272 Element.__init__(self)
275 # CHECKME: is this ok performance-wise?
276 def handleCommand(self, args):
277 if self.source_id.find(",") >=0:
278 paramlist = self.source_id.split(",")
280 for key in paramlist:
281 arg = args.get(key, [])
285 list[key] = "".join(arg)
288 self.source.handleCommand(list)
290 for c in args.get(self.source_id, []):
291 self.source.handleCommand(c)
293 def render(self, stream):
294 t = self.source.getHTML(self.source_id)
298 self.suspended = False
301 self.suspended = True
312 class MacroElement(OneTimeElement):
313 def __init__(self, id, macro_dict, macro_name):
314 OneTimeElement.__init__(self, id)
315 self.macro_dict = macro_dict
316 self.macro_name = macro_name
318 def render(self, stream):
319 self.macro_dict[self.macro_name] = self.source.getHTML(self.source_id)
321 class StreamingElement(OneTimeElement):
322 def __init__(self, id):
323 OneTimeElement.__init__(self, id)
326 def changed(self, what):
328 self.render(self.stream)
330 def setStream(self, stream):
333 # a to-be-filled list item
335 def __init__(self, name, filternum):
337 self.filternum = filternum
340 def __init__(self, macrodict, macroname):
341 self.macrodict = macrodict
342 self.macroname = macroname
344 class TextToHTML(Converter):
345 def __init__(self, arg):
346 Converter.__init__(self, arg)
348 def getHTML(self, id):
349 return self.source.text # encode & etc. here!
351 class TextToXML(Converter):
352 def __init__(self, arg):
353 Converter.__init__(self, arg)
355 def getHTML(self, id):
356 return escape_xml(self.source.text).replace("\x19", "").replace("\x1c", "").replace("\x1e", "")
358 class TextToURL(Converter):
359 def __init__(self, arg):
360 Converter.__init__(self, arg)
362 def getHTML(self, id):
363 return self.source.text.replace(" ","%20")
365 class ReturnEmptyXML(Converter):
366 def __init__(self, arg):
367 Converter.__init__(self, arg)
369 def getHTML(self, id):
370 return "<rootElement></rootElement>"
372 # a null-output. Useful if you only want to issue a command.
373 class Null(Converter):
374 def __init__(self, arg):
375 Converter.__init__(self, arg)
377 def getHTML(self, id):
380 class JavascriptUpdate(Converter):
381 def __init__(self, arg):
382 Converter.__init__(self, arg)
384 def getHTML(self, id):
385 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
386 # all other will replace this in JS
387 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '°'))
389 # the performant 'listfiller'-engine (plfe)
390 class ListFiller(Converter):
391 def __init__(self, arg):
392 Converter.__init__(self, arg)
393 # print "ListFiller-arg: ",arg
397 lut = self.source.lut
398 conv_args = self.converter_arguments
400 # now build a ["string", 1, "string", 2]-styled list, with indices into the
401 # list to avoid lookup of item name for each entry
403 for element in conv_args:
404 if isinstance(element, basestring):
405 lutlist.append((element, None))
406 elif isinstance(element, ListItem):
407 lutlist.append((lut[element.name], element.filternum))
408 elif isinstance(element, ListMacroItem):
409 lutlist.append((element.macrodict[element.macroname], None))
411 raise "neither string, ListItem nor ListMacroItem"
413 # now, for the huge list, do:
415 append = strlist.append
417 for (element, filternum) in lutlist:
421 append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
423 append(escape_xml(str(item[element])))
425 append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
427 append(str(item[element]))
428 # (this will be done in c++ later!)
430 return ''.join(strlist)
432 text = property(getText)
434 class webifHandler(ContentHandler):
435 def __init__(self, session, request):
439 self.session = session
441 self.request = request
444 def start_element(self, attrs):
447 wsource = attrs["source"]
449 path = wsource.split('.')
451 scr = self.screen.getRelatedScreen(path[0])
453 print "[webif.py] Parent Screen not found!"
457 source = scr.get(path[0])
459 if isinstance(source, ObsoleteSource):
460 # however, if we found an "obsolete source", issue warning, and resolve the real source.
461 print "WARNING: WEBIF '%s' USES OBSOLETE SOURCE '%s', USE '%s' INSTEAD!" % (name, wsource, source.new_source)
462 print "OBSOLETE SOURCE WILL BE REMOVED %s, PLEASE UPDATE!" % (source.removal_date)
463 if source.description:
464 print source.description
466 wsource = source.new_source
469 # otherwise, use that source.
472 self.source_id = str(attrs.get("id", wsource))
473 self.is_streaming = "streaming" in attrs
474 self.macro_name = attrs.get("macro") or None
476 def end_element(self):
477 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
478 if not self.is_streaming:
479 if self.macro_name is None:
480 c = OneTimeElement(self.source_id)
482 c = MacroElement(self.source_id, self.macros, self.macro_name)
484 assert self.macro_name is None
485 c = StreamingElement(self.source_id)
487 c.connect(self.source)
489 self.screen.renderer.append(c)
492 def start_convert(self, attrs):
493 ctype = attrs["type"]
495 # TODO: we need something better here
496 if ctype[:4] == "web:": # for now
497 self.converter = eval(ctype[4:])
500 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
502 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
505 def end_convert(self):
506 if len(self.sub) == 1:
507 self.sub = self.sub[0]
508 c = self.converter(self.sub)
509 c.connect(self.source)
513 def parse_item(self, attrs):
515 filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4}[attrs.get("filter", "")]
516 self.sub.append(ListItem(attrs["name"], filter))
518 assert "macro" in attrs, "e2:item must have a name= or macro= attribute!"
519 self.sub.append(ListMacroItem(self.macros, attrs["macro"]))
521 def startElement(self, name, attrs):
522 if name == "e2:screen":
523 self.screen = eval(attrs["name"])(self.session,self.request) # fixme
524 self.screens.append(self.screen)
527 if name[:3] == "e2:":
530 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
534 tag = ''.join(tag)#.encode('utf-8')
538 elif self.mode == 1: # expect "<e2:element>"
539 assert name == "e2:element", "found %s instead of e2:element" % name
540 self.start_element(attrs)
541 elif self.mode == 2: # expect "<e2:convert>"
542 if name[:3] == "e2:":
543 assert name == "e2:convert"
544 self.start_convert(attrs)
548 assert name == "e2:item", "found %s instead of e2:item!" % name
550 self.parse_item(attrs)
552 def endElement(self, name):
553 if name == "e2:screen":
557 tag = "</" + name + ">"
560 elif self.mode == 2 and name[:3] != "e2:":
562 elif self.mode == 2: # closed 'convert' -> sub
564 elif self.mode == 1: # closed 'element'
566 if name[:3] == "e2:":
569 def processingInstruction(self, target, data):
570 self.res.append('<?' + target + ' ' + data + '>')
572 def characters(self, ch):
573 ch = ch.encode('utf-8')
579 def startEntity(self, name):
580 self.res.append('&' + name + ';');
583 for screen in self.screens:
587 print "screen cleanup!"
588 for screen in self.screens:
593 def renderPage(stream, path, req, session):
595 # read in the template, create required screens
596 # we don't have persistense yet.
597 # if we had, this first part would only be done once.
598 handler = webifHandler(session,req)
599 parser = make_parser()
600 parser.setFeature(feature_namespaces, 0)
601 parser.setContentHandler(handler)
602 parser.parse(open(util.sibpath(__file__, path)))
604 # by default, we have non-streaming pages
607 # first, apply "commands" (aka. URL argument)
608 for x in handler.res:
609 if isinstance(x, Element):
610 x.handleCommand(req.args)
614 # now, we have a list with static texts mixed
615 # with non-static Elements.
616 # flatten this list, write into the stream.
617 for x in handler.res:
618 if isinstance(x, Element):
619 if isinstance(x, StreamingElement):
627 from twisted.internet import reactor
629 reactor.callLater(3, ping, s)
631 # if we met a "StreamingElement", there is at least one
632 # element which wants to output data more than once,
633 # i.e. on host-originated changes.
634 # in this case, don't finish yet, don't cleanup yet,
635 # but instead do that when the client disconnects.
637 streamFinish(handler, stream)
640 # you *need* something which constantly sends something in a regular interval,
641 # in order to detect disconnected clients.
642 # i agree that this "ping" sucks terrible, so better be sure to have something
643 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
645 stream.closed_callback = lambda : streamFinish(handler, stream)
647 def streamFinish(handler, stream):