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
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 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)
140 self["EPGNEXT"] = EPG(session,func=EPG.NEXT)
142 class MovieWebScreen(WebScreen):
143 def __init__(self, session,request):
144 WebScreen.__init__(self, session,request)
145 from Components.MovieList import MovieList
146 from Tools.Directories import resolveFilename,SCOPE_HDD
147 movielist = MovieList(eServiceReference("2:0:1:0:0:0:0:0:0:0:" + resolveFilename(SCOPE_HDD)))
148 self["MovieList"] = Movie(session,movielist,func = Movie.LIST)
149 self["MovieFileDel"] = Movie(session,movielist,func = Movie.DEL)
150 self["MovieTags"] = Movie(session,movielist,func = Movie.TAGS)
152 class MediaPlayerWebScreen(WebScreen):
153 def __init__(self, session,request):
154 WebScreen.__init__(self, session,request)
155 self["FileList"] = MP(session,func = MP.LIST)
156 self["PlayFile"] = MP(session,func = MP.PLAY)
157 self["Command"] = MP(session,func = MP.COMMAND)
158 self["WritePlaylist"] = MP(session,func = MP.WRITEPLAYLIST)
160 class FilesWebScreen(WebScreen):
161 def __init__(self, session,request):
162 WebScreen.__init__(self, session,request)
163 self["DelFile"] = Files(session,func = Files.DEL)
165 class TimerWebScreen(WebScreen):
166 def __init__(self, session,request):
167 WebScreen.__init__(self, session,request)
168 self["TimerList"] = Timer(session,func = Timer.LIST)
169 self["TimerAddEventID"] = Timer(session,func = Timer.ADDBYID)
170 self["TimerAdd"] = Timer(session,func = Timer.ADD)
171 self["TimerDel"] = Timer(session,func = Timer.DEL)
172 self["TimerChange"] = Timer(session,func = Timer.CHANGE)
173 self["TimerListWrite"] = Timer(session,func = Timer.WRITE)
174 self["TVBrowser"] = Timer(session,func = Timer.TVBROWSER)
175 self["RecordNow"] = Timer(session,func = Timer.RECNOW)
177 class RemoteWebScreen(WebScreen):
178 def __init__(self, session,request):
179 WebScreen.__init__(self, session,request)
180 self["RemoteControl"] = RemoteControl(session)
182 class PowerWebScreen(WebScreen):
183 def __init__(self, session,request):
184 WebScreen.__init__(self, session,request)
185 self["PowerState"] = PowerState(session)
187 class ParentControlWebScreen(WebScreen):
188 def __init__(self, session,request):
189 WebScreen.__init__(self, session,request)
190 self["ParentControlList"] = ParentControl(session)
192 class WAPWebScreen(WebScreen):
193 def __init__(self, session,request):
194 WebScreen.__init__(self, session,request)
195 self["WAPFillOptionListSyear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
196 self["WAPFillOptionListSday"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
197 self["WAPFillOptionListSmonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
198 self["WAPFillOptionListShour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
199 self["WAPFillOptionListSmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
201 self["WAPFillOptionListEyear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
202 self["WAPFillOptionListEday"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
203 self["WAPFillOptionListEmonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
204 self["WAPFillOptionListEhour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
205 self["WAPFillOptionListEmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
207 self["WAPFillOptionListRecord"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
208 self["WAPFillOptionListAfterEvent"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
210 self["WAPFillValueName"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
211 self["WAPFillValueDescr"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
213 self["WAPFillOptionListRepeated"] = WAPfunctions(session,func = WAPfunctions.REPEATED)
214 self["WAPServiceList"] = WAPfunctions(session, func = WAPfunctions.SERVICELIST)
216 self["WAPdeleteOldOnSave"] = WAPfunctions(session,func = WAPfunctions.DELETEOLD)
218 class StreamingWebScreen(WebScreen):
219 def __init__(self, session,request):
220 WebScreen.__init__(self, session,request)
221 from Components.Sources.StreamService import StreamService
222 self["StreamService"] = StreamService(self.session.nav)
224 class M3UStreamingWebScreen(WebScreen):
225 def __init__(self, session,request):
226 WebScreen.__init__(self, session,request)
227 from Components.Sources.StaticText import StaticText
228 from Components.Sources.Config import Config
229 from Components.config import config
230 self["ref"] = StaticText()
231 self["localip"] = RequestData(request,what=RequestData.HOST)
233 class TsM3U(WebScreen):
234 def __init__(self, session,request):
235 WebScreen.__init__(self, session,request)
236 from Components.Sources.StaticText import StaticText
237 from Components.Sources.Config import Config
238 from Components.config import config
239 self["file"] = StaticText()
240 self["localip"] = RequestData(request,what=RequestData.HOST)
242 class RestartWebScreen(WebScreen):
243 def __init__(self, session,request):
244 WebScreen.__init__(self, session,request)
246 plugin.restartWebserver()
248 class GetPid(WebScreen):
249 def __init__(self, session,request):
250 WebScreen.__init__(self, session,request)
251 from Components.Sources.StaticText import StaticText
252 from enigma import iServiceInformation
253 pids = self.session.nav.getCurrentService()
255 pidinfo = pids.info()
256 VPID = hex(pidinfo.getInfo(iServiceInformation.sVideoPID))
257 APID = hex(pidinfo.getInfo(iServiceInformation.sAudioPID))
258 PPID = hex(pidinfo.getInfo(iServiceInformation.sPMTPID))
259 self["pids"] = StaticText("%s,%s,%s"%(PPID.lstrip("0x"),VPID.lstrip("0x"),APID.lstrip("0x")))
260 self["localip"] = RequestData(request,what=RequestData.HOST)
263 # implements the 'render'-call.
264 # this will act as a downstream_element, like a renderer.
265 class OneTimeElement(Element):
266 def __init__(self, id):
267 Element.__init__(self)
270 # CHECKME: is this ok performance-wise?
271 def handleCommand(self, args):
272 if self.source_id.find(",") >=0:
273 paramlist = self.source_id.split(",")
275 for key in paramlist:
276 arg = args.get(key, [])
280 list[key] = "".join(arg)
283 self.source.handleCommand(list)
285 for c in args.get(self.source_id, []):
286 self.source.handleCommand(c)
288 def render(self, stream):
289 t = self.source.getHTML(self.source_id)
307 class MacroElement(OneTimeElement):
308 def __init__(self, id, macro_dict, macro_name):
309 OneTimeElement.__init__(self, id)
310 self.macro_dict = macro_dict
311 self.macro_name = macro_name
313 def render(self, stream):
314 self.macro_dict[self.macro_name] = self.source.getHTML(self.source_id)
316 class StreamingElement(OneTimeElement):
317 def __init__(self, id):
318 OneTimeElement.__init__(self, id)
321 def changed(self, what):
323 self.render(self.stream)
325 def setStream(self, stream):
328 # a to-be-filled list item
330 def __init__(self, name, filternum):
332 self.filternum = filternum
335 def __init__(self, macrodict, macroname):
336 self.macrodict = macrodict
337 self.macroname = macroname
339 class TextToHTML(Converter):
340 def __init__(self, arg):
341 Converter.__init__(self, arg)
343 def getHTML(self, id):
344 return self.source.text # encode & etc. here!
346 class TextToXML(Converter):
347 def __init__(self, arg):
348 Converter.__init__(self, arg)
350 def getHTML(self, id):
351 return escape_xml(self.source.text).replace("\x19", "").replace("\x1c", "").replace("\x1e", "")
353 class TextToURL(Converter):
354 def __init__(self, arg):
355 Converter.__init__(self, arg)
357 def getHTML(self, id):
358 return self.source.text.replace(" ","%20")
360 class ReturnEmptyXML(Converter):
361 def __init__(self, arg):
362 Converter.__init__(self, arg)
364 def getHTML(self, id):
365 return "<rootElement></rootElement>"
367 # a null-output. Useful if you only want to issue a command.
368 class Null(Converter):
369 def __init__(self, arg):
370 Converter.__init__(self, arg)
372 def getHTML(self, id):
375 class JavascriptUpdate(Converter):
376 def __init__(self, arg):
377 Converter.__init__(self, arg)
379 def getHTML(self, id):
380 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
381 # all other will replace this in JS
382 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '°'))
384 # the performant 'listfiller'-engine (plfe)
385 class ListFiller(Converter):
386 def __init__(self, arg):
387 Converter.__init__(self, arg)
388 # print "ListFiller-arg: ",arg
392 lut = self.source.lut
393 conv_args = self.converter_arguments
395 # now build a ["string", 1, "string", 2]-styled list, with indices into the
396 # list to avoid lookup of item name for each entry
398 for element in conv_args:
399 if isinstance(element, basestring):
400 lutlist.append((element, None))
401 elif isinstance(element, ListItem):
402 lutlist.append((lut[element.name], element.filternum))
403 elif isinstance(element, ListMacroItem):
404 lutlist.append((element.macrodict[element.macroname], None))
406 raise "neither string, ListItem nor ListMacroItem"
408 # now, for the huge list, do:
410 append = strlist.append
412 for (element, filternum) in lutlist:
416 append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
418 #append(str(item[element]).replace("&", "&").replace("<", "<").replace('"', '"').replace(">", ">"))
419 append(escape_xml(str(item[element])))
421 append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
423 append(str(item[element]))
424 # (this will be done in c++ later!)
425 return ''.join(strlist)
427 text = property(getText)
429 class webifHandler(ContentHandler):
430 def __init__(self, session, request):
434 self.session = session
436 self.request = request
439 def start_element(self, attrs):
442 wsource = attrs["source"]
444 path = wsource.split('.')
446 scr = self.screen.getRelatedScreen(path[0])
448 print "[webif.py] Parent Screen not found!"
452 source = scr.get(path[0])
454 if isinstance(source, ObsoleteSource):
455 # however, if we found an "obsolete source", issue warning, and resolve the real source.
456 print "WARNING: WEBIF '%s' USES OBSOLETE SOURCE '%s', USE '%s' INSTEAD!" % (name, wsource, source.new_source)
457 print "OBSOLETE SOURCE WILL BE REMOVED %s, PLEASE UPDATE!" % (source.removal_date)
458 if source.description:
459 print source.description
461 wsource = source.new_source
464 # otherwise, use that source.
467 self.source_id = str(attrs.get("id", wsource))
468 self.is_streaming = "streaming" in attrs
469 self.macro_name = attrs.get("macro") or None
471 def end_element(self):
472 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
473 if not self.is_streaming:
474 if self.macro_name is None:
475 c = OneTimeElement(self.source_id)
477 c = MacroElement(self.source_id, self.macros, self.macro_name)
479 assert self.macro_name is None
480 c = StreamingElement(self.source_id)
482 c.connect(self.source)
484 self.screen.renderer.append(c)
487 def start_convert(self, attrs):
488 ctype = attrs["type"]
490 # TODO: we need something better here
491 if ctype[:4] == "web:": # for now
492 self.converter = eval(ctype[4:])
495 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
497 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
500 def end_convert(self):
501 if len(self.sub) == 1:
502 self.sub = self.sub[0]
503 c = self.converter(self.sub)
504 c.connect(self.source)
508 def parse_item(self, attrs):
510 filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4}[attrs.get("filter", "")]
511 self.sub.append(ListItem(attrs["name"], filter))
513 assert "macro" in attrs, "e2:item must have a name= or macro= attribute!"
514 self.sub.append(ListMacroItem(self.macros, attrs["macro"]))
516 def startElement(self, name, attrs):
517 if name == "e2:screen":
518 self.screen = eval(attrs["name"])(self.session,self.request) # fixme
519 self.screens.append(self.screen)
522 if name[:3] == "e2:":
525 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
529 tag = ''.join(tag)#.encode('utf-8')
533 elif self.mode == 1: # expect "<e2:element>"
534 assert name == "e2:element", "found %s instead of e2:element" % name
535 self.start_element(attrs)
536 elif self.mode == 2: # expect "<e2:convert>"
537 if name[:3] == "e2:":
538 assert name == "e2:convert"
539 self.start_convert(attrs)
543 assert name == "e2:item", "found %s instead of e2:item!" % name
545 self.parse_item(attrs)
547 def endElement(self, name):
548 if name == "e2:screen":
552 tag = "</" + name + ">"
555 elif self.mode == 2 and name[:3] != "e2:":
557 elif self.mode == 2: # closed 'convert' -> sub
559 elif self.mode == 1: # closed 'element'
561 if name[:3] == "e2:":
564 def processingInstruction(self, target, data):
565 self.res.append('<?' + target + ' ' + data + '>')
567 def characters(self, ch):
568 ch = ch.encode('utf-8')
574 def startEntity(self, name):
575 self.res.append('&' + name + ';');
578 for screen in self.screens:
582 print "screen cleanup!"
583 for screen in self.screens:
588 def renderPage(stream, path, req, session):
590 # read in the template, create required screens
591 # we don't have persistense yet.
592 # if we had, this first part would only be done once.
593 handler = webifHandler(session,req)
594 parser = make_parser()
595 parser.setFeature(feature_namespaces, 0)
596 parser.setContentHandler(handler)
597 parser.parse(open(util.sibpath(__file__, path)))
599 # by default, we have non-streaming pages
602 # first, apply "commands" (aka. URL argument)
603 for x in handler.res:
604 if isinstance(x, Element):
605 x.handleCommand(req.args)
609 # now, we have a list with static texts mixed
610 # with non-static Elements.
611 # flatten this list, write into the stream.
612 for x in handler.res:
613 if isinstance(x, Element):
614 if isinstance(x, StreamingElement):
622 from twisted.internet import reactor
624 reactor.callLater(3, ping, s)
626 # if we met a "StreamingElement", there is at least one
627 # element which wants to output data more than once,
628 # i.e. on host-originated changes.
629 # in this case, don't finish yet, don't cleanup yet,
630 # but instead do that when the client disconnects.
636 # you *need* something which constantly sends something in a regular interval,
637 # in order to detect disconnected clients.
638 # i agree that this "ping" sucks terrible, so better be sure to have something
639 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
641 stream.closed_callback = lambda: handler.cleanup()