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 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 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 TextToXML(Converter):
348 def __init__(self, arg):
349 Converter.__init__(self, arg)
351 def getHTML(self, id):
352 return escape_xml(self.source.text).replace("\x19", "").replace("\x1c", "").replace("\x1e", "")
354 class TextToURL(Converter):
355 def __init__(self, arg):
356 Converter.__init__(self, arg)
358 def getHTML(self, id):
359 return self.source.text.replace(" ","%20")
361 class ReturnEmptyXML(Converter):
362 def __init__(self, arg):
363 Converter.__init__(self, arg)
365 def getHTML(self, id):
366 return "<rootElement></rootElement>"
368 # a null-output. Useful if you only want to issue a command.
369 class Null(Converter):
370 def __init__(self, arg):
371 Converter.__init__(self, arg)
373 def getHTML(self, id):
376 class JavascriptUpdate(Converter):
377 def __init__(self, arg):
378 Converter.__init__(self, arg)
380 def getHTML(self, id):
381 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
382 # all other will replace this in JS
383 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '°'))
385 # the performant 'listfiller'-engine (plfe)
386 class ListFiller(Converter):
387 def __init__(self, arg):
388 Converter.__init__(self, arg)
389 # print "ListFiller-arg: ",arg
393 lut = self.source.lut
394 conv_args = self.converter_arguments
396 # now build a ["string", 1, "string", 2]-styled list, with indices into the
397 # list to avoid lookup of item name for each entry
399 for element in conv_args:
400 if isinstance(element, basestring):
401 lutlist.append((element, None))
402 elif isinstance(element, ListItem):
403 lutlist.append((lut[element.name], element.filternum))
404 elif isinstance(element, ListMacroItem):
405 lutlist.append((element.macrodict[element.macroname], None))
407 raise "neither string, ListItem nor ListMacroItem"
409 # now, for the huge list, do:
411 append = strlist.append
413 for (element, filternum) in lutlist:
417 append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
419 #append(str(item[element]).replace("&", "&").replace("<", "<").replace('"', '"').replace(">", ">"))
420 append(escape_xml(str(item[element])))
422 append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
424 append(str(item[element]))
425 # (this will be done in c++ later!)
426 return ''.join(strlist)
428 text = property(getText)
430 class webifHandler(ContentHandler):
431 def __init__(self, session, request):
435 self.session = session
437 self.request = request
440 def start_element(self, attrs):
443 wsource = attrs["source"]
445 path = wsource.split('.')
447 scr = self.screen.getRelatedScreen(path[0])
449 print "[webif.py] Parent Screen not found!"
453 source = scr.get(path[0])
455 if isinstance(source, ObsoleteSource):
456 # however, if we found an "obsolete source", issue warning, and resolve the real source.
457 print "WARNING: WEBIF '%s' USES OBSOLETE SOURCE '%s', USE '%s' INSTEAD!" % (name, wsource, source.new_source)
458 print "OBSOLETE SOURCE WILL BE REMOVED %s, PLEASE UPDATE!" % (source.removal_date)
459 if source.description:
460 print source.description
462 wsource = source.new_source
465 # otherwise, use that source.
468 self.source_id = str(attrs.get("id", wsource))
469 self.is_streaming = "streaming" in attrs
470 self.macro_name = attrs.get("macro") or None
472 def end_element(self):
473 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
474 if not self.is_streaming:
475 if self.macro_name is None:
476 c = OneTimeElement(self.source_id)
478 c = MacroElement(self.source_id, self.macros, self.macro_name)
480 assert self.macro_name is None
481 c = StreamingElement(self.source_id)
483 c.connect(self.source)
485 self.screen.renderer.append(c)
488 def start_convert(self, attrs):
489 ctype = attrs["type"]
491 # TODO: we need something better here
492 if ctype[:4] == "web:": # for now
493 self.converter = eval(ctype[4:])
496 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
498 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
501 def end_convert(self):
502 if len(self.sub) == 1:
503 self.sub = self.sub[0]
504 c = self.converter(self.sub)
505 c.connect(self.source)
509 def parse_item(self, attrs):
511 filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4}[attrs.get("filter", "")]
512 self.sub.append(ListItem(attrs["name"], filter))
514 assert "macro" in attrs, "e2:item must have a name= or macro= attribute!"
515 self.sub.append(ListMacroItem(self.macros, attrs["macro"]))
517 def startElement(self, name, attrs):
518 if name == "e2:screen":
519 self.screen = eval(attrs["name"])(self.session,self.request) # fixme
520 self.screens.append(self.screen)
523 if name[:3] == "e2:":
526 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
530 tag = ''.join(tag)#.encode('utf-8')
534 elif self.mode == 1: # expect "<e2:element>"
535 assert name == "e2:element", "found %s instead of e2:element" % name
536 self.start_element(attrs)
537 elif self.mode == 2: # expect "<e2:convert>"
538 if name[:3] == "e2:":
539 assert name == "e2:convert"
540 self.start_convert(attrs)
544 assert name == "e2:item", "found %s instead of e2:item!" % name
546 self.parse_item(attrs)
548 def endElement(self, name):
549 if name == "e2:screen":
553 tag = "</" + name + ">"
556 elif self.mode == 2 and name[:3] != "e2:":
558 elif self.mode == 2: # closed 'convert' -> sub
560 elif self.mode == 1: # closed 'element'
562 if name[:3] == "e2:":
565 def processingInstruction(self, target, data):
566 self.res.append('<?' + target + ' ' + data + '>')
568 def characters(self, ch):
569 ch = ch.encode('utf-8')
575 def startEntity(self, name):
576 self.res.append('&' + name + ';');
579 for screen in self.screens:
583 print "screen cleanup!"
584 for screen in self.screens:
589 def renderPage(stream, path, req, session):
591 # read in the template, create required screens
592 # we don't have persistense yet.
593 # if we had, this first part would only be done once.
594 handler = webifHandler(session,req)
595 parser = make_parser()
596 parser.setFeature(feature_namespaces, 0)
597 parser.setContentHandler(handler)
598 parser.parse(open(util.sibpath(__file__, path)))
600 # by default, we have non-streaming pages
603 # first, apply "commands" (aka. URL argument)
604 for x in handler.res:
605 if isinstance(x, Element):
606 x.handleCommand(req.args)
610 # now, we have a list with static texts mixed
611 # with non-static Elements.
612 # flatten this list, write into the stream.
613 for x in handler.res:
614 if isinstance(x, Element):
615 if isinstance(x, StreamingElement):
623 from twisted.internet import reactor
625 reactor.callLater(3, ping, s)
627 # if we met a "StreamingElement", there is at least one
628 # element which wants to output data more than once,
629 # i.e. on host-originated changes.
630 # in this case, don't finish yet, don't cleanup yet,
631 # but instead do that when the client disconnects.
633 streamFinish(handler, stream)
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 : streamFinish(handler, stream)
643 def streamFinish(handler, stream):