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 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
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(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, func=AudioTracks.GET)
89 self["SelectAudioTrack"] = AudioTracks(session, func=AudioTracks.SET)
91 class AboutWebScreen(WebScreen):
92 def __init__(self, session,request):
93 WebScreen.__init__(self, session,request)
94 self["About"] = About(session)
96 class VolumeWebScreen(WebScreen):
97 def __init__(self, session,request):
98 WebScreen.__init__(self, session,request)
99 self["Volume"] = Volume(session)
101 class SettingsWebScreen(WebScreen):
102 def __init__(self, session,request):
103 WebScreen.__init__(self, session,request)
104 self["Settings"] = Settings(session)
106 class SubServiceWebScreen(WebScreen):
107 def __init__(self, session,request):
108 WebScreen.__init__(self, session,request)
109 self["SubServices"] = SubServices(session)
111 class ServiceWebScreen(WebScreen):
112 def __init__(self, session,request):
113 WebScreen.__init__(self, session,request)
114 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')
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 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 FilesWebScreen(WebScreen):
162 def __init__(self, session,request):
163 WebScreen.__init__(self, session,request)
164 self["DelFile"] = Files(session,func = Files.DEL)
166 class TimerWebScreen(WebScreen):
167 def __init__(self, session,request):
168 WebScreen.__init__(self, session,request)
169 self["TimerList"] = Timer(session,func = Timer.LIST)
170 self["TimerAddEventID"] = Timer(session,func = Timer.ADDBYID)
171 self["TimerAdd"] = Timer(session,func = Timer.ADD)
172 self["TimerDel"] = Timer(session,func = Timer.DEL)
173 self["TimerChange"] = Timer(session,func = Timer.CHANGE)
174 self["TimerListWrite"] = Timer(session,func = Timer.WRITE)
175 self["TVBrowser"] = Timer(session,func = Timer.TVBROWSER)
176 self["RecordNow"] = Timer(session,func = Timer.RECNOW)
178 class RemoteWebScreen(WebScreen):
179 def __init__(self, session,request):
180 WebScreen.__init__(self, session,request)
181 self["RemoteControl"] = RemoteControl(session)
183 class PowerWebScreen(WebScreen):
184 def __init__(self, session,request):
185 WebScreen.__init__(self, session,request)
186 self["PowerState"] = PowerState(session)
188 class ParentControlWebScreen(WebScreen):
189 def __init__(self, session,request):
190 WebScreen.__init__(self, session,request)
191 self["ParentControlList"] = ParentControl(session)
193 class WAPWebScreen(WebScreen):
194 def __init__(self, session,request):
195 WebScreen.__init__(self, session,request)
196 self["WAPFillOptionListSyear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
197 self["WAPFillOptionListSday"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
198 self["WAPFillOptionListSmonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
199 self["WAPFillOptionListShour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
200 self["WAPFillOptionListSmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
202 self["WAPFillOptionListEyear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
203 self["WAPFillOptionListEday"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
204 self["WAPFillOptionListEmonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
205 self["WAPFillOptionListEhour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
206 self["WAPFillOptionListEmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
208 self["WAPFillOptionListRecord"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
209 self["WAPFillOptionListAfterEvent"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
211 self["WAPFillValueName"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
212 self["WAPFillValueDescr"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
214 self["WAPFillOptionListRepeated"] = WAPfunctions(session,func = WAPfunctions.REPEATED)
215 self["WAPServiceList"] = WAPfunctions(session, func = WAPfunctions.SERVICELIST)
217 self["WAPdeleteOldOnSave"] = WAPfunctions(session,func = WAPfunctions.DELETEOLD)
219 class StreamingWebScreen(WebScreen):
220 def __init__(self, session,request):
221 WebScreen.__init__(self, session,request)
222 from Components.Sources.StreamService import StreamService
223 self["StreamService"] = StreamService(self.session.nav)
225 class M3UStreamingWebScreen(WebScreen):
226 def __init__(self, session,request):
227 WebScreen.__init__(self, session,request)
228 from Components.Sources.StaticText import StaticText
229 from Components.Sources.Config import Config
230 from Components.config import config
231 self["ref"] = StaticText()
232 self["localip"] = RequestData(request,what=RequestData.HOST)
234 class TsM3U(WebScreen):
235 def __init__(self, session,request):
236 WebScreen.__init__(self, session,request)
237 from Components.Sources.StaticText import StaticText
238 from Components.Sources.Config import Config
239 from Components.config import config
240 self["file"] = StaticText()
241 self["localip"] = RequestData(request,what=RequestData.HOST)
243 class RestartWebScreen(WebScreen):
244 def __init__(self, session,request):
245 WebScreen.__init__(self, session,request)
247 plugin.restartWebserver()
249 class GetPid(WebScreen):
250 def __init__(self, session,request):
251 WebScreen.__init__(self, session,request)
252 from Components.Sources.StaticText import StaticText
253 from enigma import iServiceInformation
254 pids = self.session.nav.getCurrentService()
256 pidinfo = pids.info()
257 VPID = hex(pidinfo.getInfo(iServiceInformation.sVideoPID))
258 APID = hex(pidinfo.getInfo(iServiceInformation.sAudioPID))
259 PPID = hex(pidinfo.getInfo(iServiceInformation.sPMTPID))
260 self["pids"] = StaticText("%s,%s,%s"%(PPID.lstrip("0x"),VPID.lstrip("0x"),APID.lstrip("0x")))
261 self["localip"] = RequestData(request,what=RequestData.HOST)
264 # implements the 'render'-call.
265 # this will act as a downstream_element, like a renderer.
266 class OneTimeElement(Element):
267 def __init__(self, id):
268 Element.__init__(self)
271 # CHECKME: is this ok performance-wise?
272 def handleCommand(self, args):
273 if self.source_id.find(",") >=0:
274 paramlist = self.source_id.split(",")
276 for key in paramlist:
277 arg = args.get(key, [])
281 list[key] = "".join(arg)
284 self.source.handleCommand(list)
286 for c in args.get(self.source_id, []):
287 self.source.handleCommand(c)
289 def render(self, stream):
290 t = self.source.getHTML(self.source_id)
308 class MacroElement(OneTimeElement):
309 def __init__(self, id, macro_dict, macro_name):
310 OneTimeElement.__init__(self, id)
311 self.macro_dict = macro_dict
312 self.macro_name = macro_name
314 def render(self, stream):
315 self.macro_dict[self.macro_name] = self.source.getHTML(self.source_id)
317 class StreamingElement(OneTimeElement):
318 def __init__(self, id):
319 OneTimeElement.__init__(self, id)
322 def changed(self, what):
324 self.render(self.stream)
326 def setStream(self, stream):
329 # a to-be-filled list item
331 def __init__(self, name, filternum):
333 self.filternum = filternum
336 def __init__(self, macrodict, macroname):
337 self.macrodict = macrodict
338 self.macroname = macroname
340 class TextToHTML(Converter):
341 def __init__(self, arg):
342 Converter.__init__(self, arg)
344 def getHTML(self, id):
345 return self.source.text # encode & etc. here!
347 class TextToURL(Converter):
348 def __init__(self, arg):
349 Converter.__init__(self, arg)
351 def getHTML(self, id):
352 return self.source.text.replace(" ","%20")
354 class ReturnEmptyXML(Converter):
355 def __init__(self, arg):
356 Converter.__init__(self, arg)
358 def getHTML(self, id):
359 return "<rootElement></rootElement>"
361 # a null-output. Useful if you only want to issue a command.
362 class Null(Converter):
363 def __init__(self, arg):
364 Converter.__init__(self, arg)
366 def getHTML(self, id):
369 class JavascriptUpdate(Converter):
370 def __init__(self, arg):
371 Converter.__init__(self, arg)
373 def getHTML(self, id):
374 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
375 # all other will replace this in JS
376 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '°'))
378 # the performant 'listfiller'-engine (plfe)
379 class ListFiller(Converter):
380 def __init__(self, arg):
381 Converter.__init__(self, arg)
382 # print "ListFiller-arg: ",arg
386 lut = self.source.lut
387 conv_args = self.converter_arguments
389 # now build a ["string", 1, "string", 2]-styled list, with indices into the
390 # list to avoid lookup of item name for each entry
392 for element in conv_args:
393 if isinstance(element, basestring):
394 lutlist.append((element, None))
395 elif isinstance(element, ListItem):
396 lutlist.append((lut[element.name], element.filternum))
397 elif isinstance(element, ListMacroItem):
398 lutlist.append((element.macrodict[element.macroname], None))
400 raise "neither string, ListItem nor ListMacroItem"
402 # now, for the huge list, do:
404 append = strlist.append
406 for (element, filternum) in lutlist:
410 append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
412 #append(str(item[element]).replace("&", "&").replace("<", "<").replace('"', '"').replace(">", ">"))
413 append(escape_xml(str(item[element])))
415 append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
417 append(str(item[element]))
418 # (this will be done in c++ later!)
419 return ''.join(strlist)
421 text = property(getText)
423 class webifHandler(ContentHandler):
424 def __init__(self, session, request):
428 self.session = session
430 self.request = request
433 def start_element(self, attrs):
436 wsource = attrs["source"]
438 path = wsource.split('.')
440 scr = self.screen.getRelatedScreen(path[0])
442 print "[webif.py] Parent Screen not found!"
446 source = scr.get(path[0])
448 if isinstance(source, ObsoleteSource):
449 # however, if we found an "obsolete source", issue warning, and resolve the real source.
450 print "WARNING: WEBIF '%s' USES OBSOLETE SOURCE '%s', USE '%s' INSTEAD!" % (name, wsource, source.new_source)
451 print "OBSOLETE SOURCE WILL BE REMOVED %s, PLEASE UPDATE!" % (source.removal_date)
452 if source.description:
453 print source.description
455 wsource = source.new_source
458 # otherwise, use that source.
461 self.source_id = str(attrs.get("id", wsource))
462 self.is_streaming = "streaming" in attrs
463 self.macro_name = attrs.get("macro") or None
465 def end_element(self):
466 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
467 if not self.is_streaming:
468 if self.macro_name is None:
469 c = OneTimeElement(self.source_id)
471 c = MacroElement(self.source_id, self.macros, self.macro_name)
473 assert self.macro_name is None
474 c = StreamingElement(self.source_id)
476 c.connect(self.source)
478 self.screen.renderer.append(c)
481 def start_convert(self, attrs):
482 ctype = attrs["type"]
484 # TODO: we need something better here
485 if ctype[:4] == "web:": # for now
486 self.converter = eval(ctype[4:])
489 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
491 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
494 def end_convert(self):
495 if len(self.sub) == 1:
496 self.sub = self.sub[0]
497 c = self.converter(self.sub)
498 c.connect(self.source)
502 def parse_item(self, attrs):
504 filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4}[attrs.get("filter", "")]
505 self.sub.append(ListItem(attrs["name"], filter))
507 assert "macro" in attrs, "e2:item must have a name= or macro= attribute!"
508 self.sub.append(ListMacroItem(self.macros, attrs["macro"]))
510 def startElement(self, name, attrs):
511 if name == "e2:screen":
512 self.screen = eval(attrs["name"])(self.session,self.request) # fixme
513 self.screens.append(self.screen)
516 if name[:3] == "e2:":
519 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
523 tag = ''.join(tag)#.encode('utf-8')
527 elif self.mode == 1: # expect "<e2:element>"
528 assert name == "e2:element", "found %s instead of e2:element" % name
529 self.start_element(attrs)
530 elif self.mode == 2: # expect "<e2:convert>"
531 if name[:3] == "e2:":
532 assert name == "e2:convert"
533 self.start_convert(attrs)
537 assert name == "e2:item", "found %s instead of e2:item!" % name
538 self.parse_item(attrs)
540 def endElement(self, name):
541 if name == "e2:screen":
545 tag = "</" + name + ">"
548 elif self.mode == 2 and name[:3] != "e2:":
550 elif self.mode == 2: # closed 'convert' -> sub
552 elif self.mode == 1: # closed 'element'
554 if name[:3] == "e2:":
557 def processingInstruction(self, target, data):
558 self.res.append('<?' + target + ' ' + data + '>')
560 def characters(self, ch):
561 ch = ch.encode('utf-8')
567 def startEntity(self, name):
568 self.res.append('&' + name + ';');
571 for screen in self.screens:
575 print "screen cleanup!"
576 for screen in self.screens:
581 def renderPage(stream, path, req, session):
583 # read in the template, create required screens
584 # we don't have persistense yet.
585 # if we had, this first part would only be done once.
586 handler = webifHandler(session,req)
587 parser = make_parser()
588 parser.setFeature(feature_namespaces, 0)
589 parser.setContentHandler(handler)
590 parser.parse(open(util.sibpath(__file__, path)))
592 # by default, we have non-streaming pages
595 # first, apply "commands" (aka. URL argument)
596 for x in handler.res:
597 if isinstance(x, Element):
598 x.handleCommand(req.args)
602 # now, we have a list with static texts mixed
603 # with non-static Elements.
604 # flatten this list, write into the stream.
605 for x in handler.res:
606 if isinstance(x, Element):
607 if isinstance(x, StreamingElement):
615 from twisted.internet import reactor
617 reactor.callLater(3, ping, s)
619 # if we met a "StreamingElement", there is at least one
620 # element which wants to output data more than once,
621 # i.e. on host-originated changes.
622 # in this case, don't finish yet, don't cleanup yet,
623 # but instead do that when the client disconnects.
629 # you *need* something which constantly sends something in a regular interval,
630 # in order to detect disconnected clients.
631 # i agree that this "ping" sucks terrible, so better be sure to have something
632 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
634 stream.closed_callback = lambda: handler.cleanup()