1 # -*- coding: UTF-8 -*-
4 # OK, this is more than a proof of concept
7 # - screens need to be defined somehow else.
8 # I don't know how, yet. Probably each in an own file.
9 # - more components, like the channellist
10 # - better error handling
11 # - use namespace parser
12 from enigma import eServiceReference
14 from Screens.Screen import Screen
15 from Screens.ChannelSelection import service_types_tv, service_types_radio
16 from Tools.Import import my_import
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.ServiceListReload import ServiceListReload
40 from WebComponents.Sources.AT import AT
41 from WebComponents.Sources.CurrentService import CurrentService
43 from Components.Sources.FrontendStatus import FrontendStatus
45 from Components.Converter.Converter import Converter
47 from Components.Element import Element
49 from xml.sax import make_parser
50 from xml.sax.handler import ContentHandler, feature_namespaces
51 from xml.sax.saxutils import escape as escape_xml
52 from twisted.python import util
53 from urllib2 import quote
55 # prototype of the new web frontend template system.
57 class WebScreen(Screen):
58 def __init__(self, session, request):
59 Screen.__init__(self, session)
60 self.stand_alone = True
61 self.request = request
64 class DummyWebScreen(WebScreen):
65 #use it, if you dont need any source, just to can do a static file with an xml-file
66 def __init__(self, session, request):
67 WebScreen.__init__(self, session, request)
69 class UpdateWebScreen(WebScreen):
70 def __init__(self, session, request):
71 WebScreen.__init__(self, session, request)
72 self["CurrentTime"] = Clock()
73 fav = eServiceReference(service_types_tv + ' FROM BOUQUET "bouquets.tv" ORDER BY bouquet')
75 class MessageWebScreen(WebScreen):
76 def __init__(self, session, request):
77 WebScreen.__init__(self, session, request)
78 self["Message"] = Message(session,func = Message.PRINT)
79 self["GetAnswer"] = Message(session,func = Message.ANSWER)
81 class ServiceListReloadWebScreen(WebScreen):
82 def __init__(self, session, request):
83 WebScreen.__init__(self, session, request)
84 self["ServiceListReload"] = ServiceListReload(session)
86 class AudioWebScreen(WebScreen):
87 def __init__(self, session, request):
88 WebScreen.__init__(self, session, request)
89 self["AudioTracks"] = AudioTracks(session, func=AudioTracks.GET)
90 self["SelectAudioTrack"] = AudioTracks(session, func=AudioTracks.SET)
92 class AboutWebScreen(WebScreen):
93 def __init__(self, session, request):
94 WebScreen.__init__(self, session, request)
95 self["About"] = About(session)
97 class VolumeWebScreen(WebScreen):
98 def __init__(self, session, request):
99 WebScreen.__init__(self, session, request)
100 self["Volume"] = Volume(session)
102 class SettingsWebScreen(WebScreen):
103 def __init__(self, session, request):
104 WebScreen.__init__(self, session, request)
105 self["Settings"] = Settings(session)
107 class SubServiceWebScreen(WebScreen):
108 def __init__(self, session, request):
109 WebScreen.__init__(self, session, request)
110 self["SubServices"] = SubServices(session)
112 class ServiceWebScreen(WebScreen):
113 def __init__(self, session, request):
114 WebScreen.__init__(self, session, request)
116 fav = eServiceReference(service_types_tv + ' FROM BOUQUET "bouquets.tv" ORDER BY bouquet')
117 self["SwitchService"] = ServiceList(fav, command_func = self.zapTo, validate_commands=False)
118 self["ServiceList"] = ServiceList(fav, command_func = self.getServiceList, validate_commands=False)
119 self["ServiceListRecursive"] = ServiceListRecursive(session, func=ServiceListRecursive.FETCH)
120 self["localip"] = RequestData(request,what=RequestData.HOST)
122 def getServiceList(self, sRef):
123 self["ServiceList"].root = sRef
125 def zapTo(self, reftozap):
126 from Components.config import config
127 pc = config.ParentalControl.configured.value
129 config.ParentalControl.configured.value = False
130 if config.plugins.Webinterface.allowzapping.value:
131 self.session.nav.playService(reftozap)
133 config.ParentalControl.configured.value = pc
135 switching config.ParentalControl.configured.value
136 ugly, but necessary :(
139 class EPGWebScreen(WebScreen):
140 def __init__(self, session, request):
141 WebScreen.__init__(self, session, request)
142 self["EPGTITLE"] = EPG(session,func=EPG.TITLE)
143 self["EPGSERVICE"] = EPG(session,func=EPG.SERVICE)
144 self["EPGNOW"] = EPG(session,func=EPG.NOW)
145 self["EPGNEXT"] = EPG(session,func=EPG.NEXT)
147 class MovieWebScreen(WebScreen):
148 def __init__(self, session, request):
149 WebScreen.__init__(self, session, request)
150 from Components.MovieList import MovieList
151 from Tools.Directories import resolveFilename,SCOPE_HDD
152 movielist = MovieList(eServiceReference("2:0:1:0:0:0:0:0:0:0:" + resolveFilename(SCOPE_HDD)))
153 self["MovieList"] = Movie(session,movielist,func = Movie.LIST)
154 self["MovieFileDel"] = Movie(session,movielist,func = Movie.DEL)
155 self["MovieTags"] = Movie(session,movielist,func = Movie.TAGS)
156 self["localip"] = RequestData(request,what=RequestData.HOST)
158 class MediaPlayerWebScreen(WebScreen):
159 def __init__(self, session, request):
160 WebScreen.__init__(self, session, request)
161 self["FileList"] = MP(session,func = MP.LIST)
162 self["PlayFile"] = MP(session,func = MP.PLAY)
163 self["Command"] = MP(session,func = MP.COMMAND)
164 self["WritePlaylist"] = MP(session,func = MP.WRITEPLAYLIST)
166 class AutoTimerWebScreen(WebScreen):
167 def __init__(self, session, request):
168 WebScreen.__init__(self, session, request)
169 self["AutoTimerList"] = AT(session,func = AT.LIST)
170 self["AutoTimerWrite"] = AT(session,func = AT.WRITE)
172 class TimerWebScreen(WebScreen):
173 def __init__(self, session, request):
174 WebScreen.__init__(self, session, request)
175 self["TimerList"] = Timer(session,func = Timer.LIST)
176 self["TimerAddEventID"] = Timer(session,func = Timer.ADDBYID)
177 self["TimerAdd"] = Timer(session,func = Timer.ADD)
178 self["TimerDel"] = Timer(session,func = Timer.DEL)
179 self["TimerChange"] = Timer(session,func = Timer.CHANGE)
180 self["TimerListWrite"] = Timer(session,func = Timer.WRITE)
181 self["TVBrowser"] = Timer(session,func = Timer.TVBROWSER)
182 self["RecordNow"] = Timer(session,func = Timer.RECNOW)
184 class RemoteWebScreen(WebScreen):
185 def __init__(self, session, request):
186 WebScreen.__init__(self, session, request)
187 self["RemoteControl"] = RemoteControl(session)
189 class PowerWebScreen(WebScreen):
190 def __init__(self, session, request):
191 WebScreen.__init__(self, session, request)
192 self["PowerState"] = PowerState(session)
194 class ParentControlWebScreen(WebScreen):
195 def __init__(self, session, request):
196 WebScreen.__init__(self, session, request)
197 self["ParentControlList"] = ParentControl(session)
199 class WAPWebScreen(WebScreen):
200 def __init__(self, session, request):
201 WebScreen.__init__(self, session, request)
202 self["WAPFillOptionListSyear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
203 self["WAPFillOptionListSday"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
204 self["WAPFillOptionListSmonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
205 self["WAPFillOptionListShour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
206 self["WAPFillOptionListSmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
208 self["WAPFillOptionListEyear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
209 self["WAPFillOptionListEday"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
210 self["WAPFillOptionListEmonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
211 self["WAPFillOptionListEhour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
212 self["WAPFillOptionListEmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
214 self["WAPFillOptionListRecord"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
215 self["WAPFillOptionListAfterEvent"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
217 self["WAPFillValueName"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
218 self["WAPFillValueDescr"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
220 self["WAPFillOptionListRepeated"] = WAPfunctions(session,func = WAPfunctions.REPEATED)
221 self["WAPServiceList"] = WAPfunctions(session, func = WAPfunctions.SERVICELIST)
223 self["WAPdeleteOldOnSave"] = WAPfunctions(session,func = WAPfunctions.DELETEOLD)
225 class StreamingWebScreen(WebScreen):
226 def __init__(self, session, request):
227 WebScreen.__init__(self, session, request)
228 from Components.Sources.StreamService import StreamService
229 self["StreamService"] = StreamService(self.session.nav)
231 class M3UStreamingWebScreen(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["ref"] = StaticText()
238 self["localip"] = RequestData(request,what=RequestData.HOST)
240 class M3UStreamingCurrentServiceWebScreen(WebScreen):
241 def __init__(self, session, request):
242 WebScreen.__init__(self, session, request)
243 self["CurrentService"] = CurrentService(session)
244 self["localip"] = RequestData(request,what=RequestData.HOST)
246 class TsM3U(WebScreen):
247 def __init__(self, session, request):
248 WebScreen.__init__(self, session, request)
249 from Components.Sources.StaticText import StaticText
250 from Components.Sources.Config import Config
251 from Components.config import config
252 self["file"] = StaticText()
253 self["localip"] = RequestData(request,what=RequestData.HOST)
255 class RestartWebScreen(WebScreen):
256 def __init__(self, session, request):
257 WebScreen.__init__(self, session, request)
259 plugin.restartWebserver(session)
261 class GetPid(WebScreen):
262 def __init__(self, session, request):
263 WebScreen.__init__(self, session, request)
264 from Components.Sources.StaticText import StaticText
265 from enigma import iServiceInformation
266 pids = self.session.nav.getCurrentService()
268 pidinfo = pids.info()
269 VPID = hex(pidinfo.getInfo(iServiceInformation.sVideoPID))
270 APID = hex(pidinfo.getInfo(iServiceInformation.sAudioPID))
271 PPID = hex(pidinfo.getInfo(iServiceInformation.sPMTPID))
272 self["pids"] = StaticText("%s,%s,%s"%(PPID.lstrip("0x"),VPID.lstrip("0x"),APID.lstrip("0x")))
274 self["pids"] = StaticText("0x,0x,0x")
276 self["localip"] = RequestData(request,what=RequestData.HOST)
279 # implements the 'render'-call.
280 # this will act as a downstream_element, like a renderer.
281 class OneTimeElement(Element):
282 def __init__(self, id):
283 Element.__init__(self)
286 # CHECKME: is this ok performance-wise?
287 def handleCommand(self, args):
288 if self.source_id.find(",") >=0:
289 paramlist = self.source_id.split(",")
291 for key in paramlist:
292 arg = args.get(key, [])
296 list[key] = "".join(arg)
299 self.source.handleCommand(list)
301 for c in args.get(self.source_id, []):
302 self.source.handleCommand(c)
304 def render(self, stream):
305 t = self.source.getHTML(self.source_id)
309 self.suspended = False
312 self.suspended = True
323 class MacroElement(OneTimeElement):
324 def __init__(self, id, macro_dict, macro_name):
325 OneTimeElement.__init__(self, id)
326 self.macro_dict = macro_dict
327 self.macro_name = macro_name
329 def render(self, stream):
330 self.macro_dict[self.macro_name] = self.source.getHTML(self.source_id)
332 class StreamingElement(OneTimeElement):
333 def __init__(self, id):
334 OneTimeElement.__init__(self, id)
337 def changed(self, what):
339 self.render(self.stream)
341 def setStream(self, stream):
344 # a to-be-filled list item
346 def __init__(self, name, filternum):
348 self.filternum = filternum
351 def __init__(self, macrodict, macroname):
352 self.macrodict = macrodict
353 self.macroname = macroname
355 class TextToHTML(Converter):
356 def __init__(self, arg):
357 Converter.__init__(self, arg)
359 def getHTML(self, id):
360 return self.source.text # encode & etc. here!
362 class TextToXML(Converter):
363 def __init__(self, arg):
364 Converter.__init__(self, arg)
366 def getHTML(self, id):
367 return escape_xml(self.source.text).replace("\x19", "").replace("\x1c", "").replace("\x1e", "")
369 class TextToURL(Converter):
370 def __init__(self, arg):
371 Converter.__init__(self, arg)
373 def getHTML(self, id):
374 return self.source.text.replace(" ","%20")
376 class ReturnEmptyXML(Converter):
377 def __init__(self, arg):
378 Converter.__init__(self, arg)
380 def getHTML(self, id):
381 return "<rootElement></rootElement>"
383 # a null-output. Useful if you only want to issue a command.
384 class Null(Converter):
385 def __init__(self, arg):
386 Converter.__init__(self, arg)
388 def getHTML(self, id):
391 class JavascriptUpdate(Converter):
392 def __init__(self, arg):
393 Converter.__init__(self, arg)
395 def getHTML(self, id):
396 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
397 # all other will replace this in JS
398 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '°'))
400 # the performant 'listfiller'-engine (plfe)
401 class ListFiller(Converter):
402 def __init__(self, arg):
403 Converter.__init__(self, arg)
404 # print "ListFiller-arg: ",arg
408 lut = self.source.lut
409 conv_args = self.converter_arguments
411 # now build a ["string", 1, "string", 2]-styled list, with indices into the
412 # list to avoid lookup of item name for each entry
414 for element in conv_args:
415 if isinstance(element, basestring):
416 lutlist.append((element, None))
417 elif isinstance(element, ListItem):
418 lutlist.append((lut[element.name], element.filternum))
419 elif isinstance(element, ListMacroItem):
420 lutlist.append((element.macrodict[element.macroname], None))
422 raise "neither string, ListItem nor ListMacroItem"
424 # now, for the huge list, do:
426 append = strlist.append
428 for (element, filternum) in lutlist:
432 append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
434 append(escape_xml(str(item[element])))
436 append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
438 append(quote(str(item[element])))
440 append(str(item[element]))
441 # (this will be done in c++ later!)
443 return ''.join(strlist)
445 text = property(getText)
447 class webifHandler(ContentHandler):
448 def __init__(self, session, request):
452 self.session = session
454 self.request = request
457 def start_element(self, attrs):
460 wsource = attrs["source"]
462 path = wsource.split('.')
464 scr = self.screen.getRelatedScreen(path[0])
466 print "[webif.py] Parent Screen not found!"
470 source = scr.get(path[0])
472 if isinstance(source, ObsoleteSource):
473 # however, if we found an "obsolete source", issue warning, and resolve the real source.
474 print "WARNING: WEBIF '%s' USES OBSOLETE SOURCE '%s', USE '%s' INSTEAD!" % (name, wsource, source.new_source)
475 print "OBSOLETE SOURCE WILL BE REMOVED %s, PLEASE UPDATE!" % (source.removal_date)
476 if source.description:
477 print source.description
479 wsource = source.new_source
482 # otherwise, use that source.
485 self.source_id = str(attrs.get("id", wsource))
486 self.is_streaming = "streaming" in attrs
487 self.macro_name = attrs.get("macro") or None
489 def end_element(self):
490 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
491 if not self.is_streaming:
492 if self.macro_name is None:
493 c = OneTimeElement(self.source_id)
495 c = MacroElement(self.source_id, self.macros, self.macro_name)
497 assert self.macro_name is None
498 c = StreamingElement(self.source_id)
500 c.connect(self.source)
502 self.screen.renderer.append(c)
505 def start_convert(self, attrs):
506 ctype = attrs["type"]
508 # TODO: we need something better here
509 if ctype[:4] == "web:": # for now
510 self.converter = eval(ctype[4:])
513 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
515 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
518 def end_convert(self):
519 if len(self.sub) == 1:
520 self.sub = self.sub[0]
521 c = self.converter(self.sub)
522 c.connect(self.source)
526 def parse_item(self, attrs):
528 filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4, "urlencode": 5}[attrs.get("filter", "")]
529 self.sub.append(ListItem(attrs["name"], filter))
531 assert "macro" in attrs, "e2:item must have a name= or macro= attribute!"
532 self.sub.append(ListMacroItem(self.macros, attrs["macro"]))
534 def startElement(self, name, attrs):
535 if name == "e2:screen":
536 self.screen = eval(attrs["name"])(self.session,self.request) # fixme
537 self.screens.append(self.screen)
540 if name[:3] == "e2:":
543 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
547 tag = ''.join(tag)#.encode('utf-8')
551 elif self.mode == 1: # expect "<e2:element>"
552 assert name == "e2:element", "found %s instead of e2:element" % name
553 self.start_element(attrs)
554 elif self.mode == 2: # expect "<e2:convert>"
555 if name[:3] == "e2:":
556 assert name == "e2:convert"
557 self.start_convert(attrs)
561 assert name == "e2:item", "found %s instead of e2:item!" % name
563 self.parse_item(attrs)
565 def endElement(self, name):
566 if name == "e2:screen":
570 tag = "</" + name + ">"
573 elif self.mode == 2 and name[:3] != "e2:":
575 elif self.mode == 2: # closed 'convert' -> sub
577 elif self.mode == 1: # closed 'element'
579 if name[:3] == "e2:":
582 def processingInstruction(self, target, data):
583 self.res.append('<?' + target + ' ' + data + '>')
585 def characters(self, ch):
586 ch = ch.encode('utf-8')
592 def startEntity(self, name):
593 self.res.append('&' + name + ';');
596 for screen in self.screens:
600 print "screen cleanup!"
601 for screen in self.screens:
606 def renderPage(stream, path, req, session):
608 # read in the template, create required screens
609 # we don't have persistense yet.
610 # if we had, this first part would only be done once.
611 handler = webifHandler(session,req)
612 parser = make_parser()
613 parser.setFeature(feature_namespaces, 0)
614 parser.setContentHandler(handler)
615 parser.parse(open(util.sibpath(__file__, path)))
617 # by default, we have non-streaming pages
620 # first, apply "commands" (aka. URL argument)
621 for x in handler.res:
622 if isinstance(x, Element):
623 x.handleCommand(req.args)
627 # now, we have a list with static texts mixed
628 # with non-static Elements.
629 # flatten this list, write into the stream.
630 for x in handler.res:
631 if isinstance(x, Element):
632 if isinstance(x, StreamingElement):
640 from twisted.internet import reactor
642 reactor.callLater(3, ping, s)
644 # if we met a "StreamingElement", there is at least one
645 # element which wants to output data more than once,
646 # i.e. on host-originated changes.
647 # in this case, don't finish yet, don't cleanup yet,
648 # but instead do that when the client disconnects.
650 streamFinish(handler, stream)
653 # you *need* something which constantly sends something in a regular interval,
654 # in order to detect disconnected clients.
655 # i agree that this "ping" sucks terrible, so better be sure to have something
656 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
658 stream.closed_callback = lambda : streamFinish(handler, stream)
660 def streamFinish(handler, stream):