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 Components.Sources.Source import ObsoleteSource
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
38 from WebComponents.Sources.ServiceListReload import ServiceListReload
40 from Components.Sources.FrontendStatus import FrontendStatus
42 from Components.Converter.Converter import Converter
44 from Components.Element import Element
46 from xml.sax import make_parser
47 from xml.sax.handler import ContentHandler, feature_namespaces
48 from xml.sax.saxutils import escape as escape_xml
49 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(WebScreen):
66 def __init__(self, session,request):
67 WebScreen.__init__(self, session,request)
68 self["CurrentTime"] = Clock()
69 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')
71 class MessageWebScreen(WebScreen):
72 def __init__(self, session,request):
73 WebScreen.__init__(self, session,request)
74 self["Message"] = Message(session,func = Message.PRINT)
75 self["GetAnswer"] = Message(session,func = Message.ANSWER)
77 class ServiceListReloadWebScreen(WebScreen):
78 def __init__(self, session,request):
79 WebScreen.__init__(self, session,request)
80 self["ServiceListReload"] = ServiceListReload(session)
82 class AudioWebScreen(WebScreen):
83 def __init__(self, session,request):
84 WebScreen.__init__(self, session,request)
85 self["AudioTracks"] = AudioTracks(session, func=AudioTracks.GET)
86 self["SelectAudioTrack"] = AudioTracks(session, func=AudioTracks.SET)
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)
138 self["EPGNEXT"] = EPG(session,func=EPG.NEXT)
140 class MovieWebScreen(WebScreen):
141 def __init__(self, session,request):
142 WebScreen.__init__(self, session,request)
143 from Components.MovieList import MovieList
144 from Tools.Directories import resolveFilename,SCOPE_HDD
145 movielist = MovieList(eServiceReference("2:0:1:0:0:0:0:0:0:0:" + resolveFilename(SCOPE_HDD)))
146 self["MovieList"] = Movie(session,movielist,func = Movie.LIST)
147 self["MovieFileDel"] = Movie(session,movielist,func = Movie.DEL)
148 self["MovieTags"] = Movie(session,movielist,func = Movie.TAGS)
150 class MediaPlayerWebScreen(WebScreen):
151 def __init__(self, session,request):
152 WebScreen.__init__(self, session,request)
153 self["FileList"] = MP(session,func = MP.LIST)
154 self["PlayFile"] = MP(session,func = MP.PLAY)
155 self["Command"] = MP(session,func = MP.COMMAND)
156 self["WritePlaylist"] = MP(session,func = MP.WRITEPLAYLIST)
158 class FilesWebScreen(WebScreen):
159 def __init__(self, session,request):
160 WebScreen.__init__(self, session,request)
161 self["DelFile"] = Files(session,func = Files.DEL)
163 class TimerWebScreen(WebScreen):
164 def __init__(self, session,request):
165 WebScreen.__init__(self, session,request)
166 self["TimerList"] = Timer(session,func = Timer.LIST)
167 self["TimerAddEventID"] = Timer(session,func = Timer.ADDBYID)
168 self["TimerAdd"] = Timer(session,func = Timer.ADD)
169 self["TimerDel"] = Timer(session,func = Timer.DEL)
170 self["TimerChange"] = Timer(session,func = Timer.CHANGE)
171 self["TimerListWrite"] = Timer(session,func = Timer.WRITE)
172 self["TVBrowser"] = Timer(session,func = Timer.TVBROWSER)
173 self["RecordNow"] = Timer(session,func = Timer.RECNOW)
175 class RemoteWebScreen(WebScreen):
176 def __init__(self, session,request):
177 WebScreen.__init__(self, session,request)
178 self["RemoteControl"] = RemoteControl(session)
180 class PowerWebScreen(WebScreen):
181 def __init__(self, session,request):
182 WebScreen.__init__(self, session,request)
183 self["PowerState"] = PowerState(session)
185 class ParentControlWebScreen(WebScreen):
186 def __init__(self, session,request):
187 WebScreen.__init__(self, session,request)
188 self["ParentControlList"] = ParentControl(session)
190 class WAPWebScreen(WebScreen):
191 def __init__(self, session,request):
192 WebScreen.__init__(self, session,request)
193 self["WAPFillOptionListSyear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
194 self["WAPFillOptionListSday"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
195 self["WAPFillOptionListSmonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
196 self["WAPFillOptionListShour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
197 self["WAPFillOptionListSmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
199 self["WAPFillOptionListEyear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
200 self["WAPFillOptionListEday"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
201 self["WAPFillOptionListEmonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
202 self["WAPFillOptionListEhour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
203 self["WAPFillOptionListEmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
205 self["WAPFillOptionListRecord"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
206 self["WAPFillOptionListAfterEvent"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
208 self["WAPFillValueName"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
209 self["WAPFillValueDescr"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
211 self["WAPFillOptionListRepeated"] = WAPfunctions(session,func = WAPfunctions.REPEATED)
212 self["WAPServiceList"] = WAPfunctions(session, func = WAPfunctions.SERVICELIST)
214 self["WAPdeleteOldOnSave"] = WAPfunctions(session,func = WAPfunctions.DELETEOLD)
216 class StreamingWebScreen(WebScreen):
217 def __init__(self, session,request):
218 WebScreen.__init__(self, session,request)
219 from Components.Sources.StreamService import StreamService
220 self["StreamService"] = StreamService(self.session.nav)
222 class M3UStreamingWebScreen(WebScreen):
223 def __init__(self, session,request):
224 WebScreen.__init__(self, session,request)
225 from Components.Sources.StaticText import StaticText
226 from Components.Sources.Config import Config
227 from Components.config import config
228 self["ref"] = StaticText()
229 self["localip"] = RequestData(request,what=RequestData.HOST)
231 class TsM3U(WebScreen):
232 def __init__(self, session,request):
233 WebScreen.__init__(self, session,request)
234 from Components.Sources.StaticText import StaticText
235 from Components.Sources.Config import Config
236 from Components.config import config
237 self["file"] = StaticText()
238 self["localip"] = RequestData(request,what=RequestData.HOST)
240 class RestartWebScreen(WebScreen):
241 def __init__(self, session,request):
242 WebScreen.__init__(self, session,request)
244 plugin.restartWebserver()
246 class GetPid(WebScreen):
247 def __init__(self, session,request):
248 WebScreen.__init__(self, session,request)
249 from Components.Sources.StaticText import StaticText
250 from enigma import iServiceInformation
251 pids = self.session.nav.getCurrentService()
253 pidinfo = pids.info()
254 VPID = hex(pidinfo.getInfo(iServiceInformation.sVideoPID))
255 APID = hex(pidinfo.getInfo(iServiceInformation.sAudioPID))
256 PPID = hex(pidinfo.getInfo(iServiceInformation.sPMTPID))
257 self["pids"] = StaticText("%s,%s,%s"%(PPID.lstrip("0x"),VPID.lstrip("0x"),APID.lstrip("0x")))
258 self["localip"] = RequestData(request,what=RequestData.HOST)
261 # implements the 'render'-call.
262 # this will act as a downstream_element, like a renderer.
263 class OneTimeElement(Element):
264 def __init__(self, id):
265 Element.__init__(self)
268 # CHECKME: is this ok performance-wise?
269 def handleCommand(self, args):
270 if self.source_id.find(",") >=0:
271 paramlist = self.source_id.split(",")
273 for key in paramlist:
274 arg = args.get(key, [])
278 list[key] = "".join(arg)
281 self.source.handleCommand(list)
283 for c in args.get(self.source_id, []):
284 self.source.handleCommand(c)
286 def render(self, stream):
287 t = self.source.getHTML(self.source_id)
305 class MacroElement(OneTimeElement):
306 def __init__(self, id, macro_dict, macro_name):
307 OneTimeElement.__init__(self, id)
308 self.macro_dict = macro_dict
309 self.macro_name = macro_name
311 def render(self, stream):
312 self.macro_dict[self.macro_name] = self.source.getHTML(self.source_id)
314 class StreamingElement(OneTimeElement):
315 def __init__(self, id):
316 OneTimeElement.__init__(self, id)
319 def changed(self, what):
321 self.render(self.stream)
323 def setStream(self, stream):
326 # a to-be-filled list item
328 def __init__(self, name, filternum):
330 self.filternum = filternum
333 def __init__(self, macrodict, macroname):
334 self.macrodict = macrodict
335 self.macroname = macroname
337 class TextToHTML(Converter):
338 def __init__(self, arg):
339 Converter.__init__(self, arg)
341 def getHTML(self, id):
342 return self.source.text # encode & etc. here!
344 class TextToXML(Converter):
345 def __init__(self, arg):
346 Converter.__init__(self, arg)
348 def getHTML(self, id):
349 return escape_xml(self.source.text).replace("\x19", "").replace("\x1c", "").replace("\x1e", "")
351 class TextToURL(Converter):
352 def __init__(self, arg):
353 Converter.__init__(self, arg)
355 def getHTML(self, id):
356 return self.source.text.replace(" ","%20")
358 class ReturnEmptyXML(Converter):
359 def __init__(self, arg):
360 Converter.__init__(self, arg)
362 def getHTML(self, id):
363 return "<rootElement></rootElement>"
365 # a null-output. Useful if you only want to issue a command.
366 class Null(Converter):
367 def __init__(self, arg):
368 Converter.__init__(self, arg)
370 def getHTML(self, id):
373 class JavascriptUpdate(Converter):
374 def __init__(self, arg):
375 Converter.__init__(self, arg)
377 def getHTML(self, id):
378 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
379 # all other will replace this in JS
380 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '°'))
382 # the performant 'listfiller'-engine (plfe)
383 class ListFiller(Converter):
384 def __init__(self, arg):
385 Converter.__init__(self, arg)
386 # print "ListFiller-arg: ",arg
390 lut = self.source.lut
391 conv_args = self.converter_arguments
393 # now build a ["string", 1, "string", 2]-styled list, with indices into the
394 # list to avoid lookup of item name for each entry
396 for element in conv_args:
397 if isinstance(element, basestring):
398 lutlist.append((element, None))
399 elif isinstance(element, ListItem):
400 lutlist.append((lut[element.name], element.filternum))
401 elif isinstance(element, ListMacroItem):
402 lutlist.append((element.macrodict[element.macroname], None))
404 raise "neither string, ListItem nor ListMacroItem"
406 # now, for the huge list, do:
408 append = strlist.append
410 for (element, filternum) in lutlist:
414 append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
416 #append(str(item[element]).replace("&", "&").replace("<", "<").replace('"', '"').replace(">", ">"))
417 append(escape_xml(str(item[element])))
419 append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
421 append(str(item[element]))
422 # (this will be done in c++ later!)
423 return ''.join(strlist)
425 text = property(getText)
427 class webifHandler(ContentHandler):
428 def __init__(self, session, request):
432 self.session = session
434 self.request = request
437 def start_element(self, attrs):
440 wsource = attrs["source"]
442 path = wsource.split('.')
444 scr = self.screen.getRelatedScreen(path[0])
446 print "[webif.py] Parent Screen not found!"
450 source = scr.get(path[0])
452 if isinstance(source, ObsoleteSource):
453 # however, if we found an "obsolete source", issue warning, and resolve the real source.
454 print "WARNING: WEBIF '%s' USES OBSOLETE SOURCE '%s', USE '%s' INSTEAD!" % (name, wsource, source.new_source)
455 print "OBSOLETE SOURCE WILL BE REMOVED %s, PLEASE UPDATE!" % (source.removal_date)
456 if source.description:
457 print source.description
459 wsource = source.new_source
462 # otherwise, use that source.
465 self.source_id = str(attrs.get("id", wsource))
466 self.is_streaming = "streaming" in attrs
467 self.macro_name = attrs.get("macro") or None
469 def end_element(self):
470 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
471 if not self.is_streaming:
472 if self.macro_name is None:
473 c = OneTimeElement(self.source_id)
475 c = MacroElement(self.source_id, self.macros, self.macro_name)
477 assert self.macro_name is None
478 c = StreamingElement(self.source_id)
480 c.connect(self.source)
482 self.screen.renderer.append(c)
485 def start_convert(self, attrs):
486 ctype = attrs["type"]
488 # TODO: we need something better here
489 if ctype[:4] == "web:": # for now
490 self.converter = eval(ctype[4:])
493 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
495 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
498 def end_convert(self):
499 if len(self.sub) == 1:
500 self.sub = self.sub[0]
501 c = self.converter(self.sub)
502 c.connect(self.source)
506 def parse_item(self, attrs):
508 filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4}[attrs.get("filter", "")]
509 self.sub.append(ListItem(attrs["name"], filter))
511 assert "macro" in attrs, "e2:item must have a name= or macro= attribute!"
512 self.sub.append(ListMacroItem(self.macros, attrs["macro"]))
514 def startElement(self, name, attrs):
515 if name == "e2:screen":
516 self.screen = eval(attrs["name"])(self.session,self.request) # fixme
517 self.screens.append(self.screen)
520 if name[:3] == "e2:":
523 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
527 tag = ''.join(tag)#.encode('utf-8')
531 elif self.mode == 1: # expect "<e2:element>"
532 assert name == "e2:element", "found %s instead of e2:element" % name
533 self.start_element(attrs)
534 elif self.mode == 2: # expect "<e2:convert>"
535 if name[:3] == "e2:":
536 assert name == "e2:convert"
537 self.start_convert(attrs)
541 assert name == "e2:item", "found %s instead of e2:item!" % name
543 self.parse_item(attrs)
545 def endElement(self, name):
546 if name == "e2:screen":
550 tag = "</" + name + ">"
553 elif self.mode == 2 and name[:3] != "e2:":
555 elif self.mode == 2: # closed 'convert' -> sub
557 elif self.mode == 1: # closed 'element'
559 if name[:3] == "e2:":
562 def processingInstruction(self, target, data):
563 self.res.append('<?' + target + ' ' + data + '>')
565 def characters(self, ch):
566 ch = ch.encode('utf-8')
572 def startEntity(self, name):
573 self.res.append('&' + name + ';');
576 for screen in self.screens:
580 print "screen cleanup!"
581 for screen in self.screens:
586 def renderPage(stream, path, req, session):
588 # read in the template, create required screens
589 # we don't have persistense yet.
590 # if we had, this first part would only be done once.
591 handler = webifHandler(session,req)
592 parser = make_parser()
593 parser.setFeature(feature_namespaces, 0)
594 parser.setContentHandler(handler)
595 parser.parse(open(util.sibpath(__file__, path)))
597 # by default, we have non-streaming pages
600 # first, apply "commands" (aka. URL argument)
601 for x in handler.res:
602 if isinstance(x, Element):
603 x.handleCommand(req.args)
607 # now, we have a list with static texts mixed
608 # with non-static Elements.
609 # flatten this list, write into the stream.
610 for x in handler.res:
611 if isinstance(x, Element):
612 if isinstance(x, StreamingElement):
620 from twisted.internet import reactor
622 reactor.callLater(3, ping, s)
624 # if we met a "StreamingElement", there is at least one
625 # element which wants to output data more than once,
626 # i.e. on host-originated changes.
627 # in this case, don't finish yet, don't cleanup yet,
628 # but instead do that when the client disconnects.
634 # you *need* something which constantly sends something in a regular interval,
635 # in order to detect disconnected clients.
636 # i agree that this "ping" sucks terrible, so better be sure to have something
637 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
639 stream.closed_callback = lambda: handler.cleanup()