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()
74 class MessageWebScreen(WebScreen):
75 def __init__(self, session, request):
76 WebScreen.__init__(self, session, request)
77 self["Message"] = Message(session,func = Message.PRINT)
78 self["GetAnswer"] = Message(session,func = Message.ANSWER)
80 class ServiceListReloadWebScreen(WebScreen):
81 def __init__(self, session, request):
82 WebScreen.__init__(self, session, request)
83 self["ServiceListReload"] = ServiceListReload(session)
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)
115 fav = eServiceReference(service_types_tv + ' FROM BOUQUET "bouquets.tv" ORDER BY bouquet')
116 self["SwitchService"] = ServiceList(fav, command_func = self.zapTo, validate_commands=False)
117 self["ServiceList"] = ServiceList(fav, command_func = self.getServiceList, validate_commands=False)
118 self["ServiceListRecursive"] = ServiceListRecursive(session, func=ServiceListRecursive.FETCH)
119 self["localip"] = RequestData(request,what=RequestData.HOST)
121 def getServiceList(self, sRef):
122 self["ServiceList"].root = sRef
124 def zapTo(self, reftozap):
125 from Components.config import config
126 pc = config.ParentalControl.configured.value
128 config.ParentalControl.configured.value = False
129 if config.plugins.Webinterface.allowzapping.value:
130 self.session.nav.playService(reftozap)
132 config.ParentalControl.configured.value = pc
134 switching config.ParentalControl.configured.value
135 ugly, but necessary :(
138 class EPGWebScreen(WebScreen):
139 def __init__(self, session, request):
140 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)
146 self["EPGBOUQUET"] = EPG(session,func=EPG.BOUQUET)
148 def getServiceList(self, sRef):
149 self["ServiceList"].root = sRef
151 class MovieWebScreen(WebScreen):
152 def __init__(self, session, request):
153 WebScreen.__init__(self, session, request)
154 from Components.MovieList import MovieList
155 from Tools.Directories import resolveFilename,SCOPE_HDD
156 movielist = MovieList(eServiceReference("2:0:1:0:0:0:0:0:0:0:" + resolveFilename(SCOPE_HDD)))
157 self["MovieList"] = Movie(session,movielist,func = Movie.LIST)
158 self["MovieFileDel"] = Movie(session,movielist,func = Movie.DEL)
159 self["MovieTags"] = Movie(session,movielist,func = Movie.TAGS)
160 self["localip"] = RequestData(request,what=RequestData.HOST)
162 class MediaPlayerWebScreen(WebScreen):
163 def __init__(self, session, request):
164 WebScreen.__init__(self, session, request)
165 self["FileList"] = MP(session,func = MP.LIST)
166 self["PlayFile"] = MP(session,func = MP.PLAY)
167 self["Command"] = MP(session,func = MP.COMMAND)
168 self["WritePlaylist"] = MP(session,func = MP.WRITEPLAYLIST)
170 class AutoTimerWebScreen(WebScreen):
171 def __init__(self, session, request):
172 WebScreen.__init__(self, session, request)
173 self["AutoTimerList"] = AT(session,func = AT.LIST)
174 self["AutoTimerWrite"] = AT(session,func = AT.WRITE)
176 class TimerWebScreen(WebScreen):
177 def __init__(self, session, request):
178 WebScreen.__init__(self, session, request)
179 self["TimerList"] = Timer(session,func = Timer.LIST)
180 self["TimerAddEventID"] = Timer(session,func = Timer.ADDBYID)
181 self["TimerAdd"] = Timer(session,func = Timer.ADD)
182 self["TimerDel"] = Timer(session,func = Timer.DEL)
183 self["TimerChange"] = Timer(session,func = Timer.CHANGE)
184 self["TimerListWrite"] = Timer(session,func = Timer.WRITE)
185 self["TVBrowser"] = Timer(session,func = Timer.TVBROWSER)
186 self["RecordNow"] = Timer(session,func = Timer.RECNOW)
188 class RemoteWebScreen(WebScreen):
189 def __init__(self, session, request):
190 WebScreen.__init__(self, session, request)
191 self["RemoteControl"] = RemoteControl(session)
193 class PowerWebScreen(WebScreen):
194 def __init__(self, session, request):
195 WebScreen.__init__(self, session, request)
196 self["PowerState"] = PowerState(session)
198 class ParentControlWebScreen(WebScreen):
199 def __init__(self, session, request):
200 WebScreen.__init__(self, session, request)
201 self["ParentControlList"] = ParentControl(session)
203 class WAPWebScreen(WebScreen):
204 def __init__(self, session, request):
205 WebScreen.__init__(self, session, request)
206 self["WAPFillOptionListSyear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
207 self["WAPFillOptionListSday"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
208 self["WAPFillOptionListSmonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
209 self["WAPFillOptionListShour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
210 self["WAPFillOptionListSmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
212 self["WAPFillOptionListEyear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
213 self["WAPFillOptionListEday"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
214 self["WAPFillOptionListEmonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
215 self["WAPFillOptionListEhour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
216 self["WAPFillOptionListEmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
218 self["WAPFillOptionListRecord"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
219 self["WAPFillOptionListAfterEvent"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
221 self["WAPFillValueName"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
222 self["WAPFillValueDescr"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
224 self["WAPFillOptionListRepeated"] = WAPfunctions(session,func = WAPfunctions.REPEATED)
225 self["WAPServiceList"] = WAPfunctions(session, func = WAPfunctions.SERVICELIST)
227 self["WAPdeleteOldOnSave"] = WAPfunctions(session,func = WAPfunctions.DELETEOLD)
229 class StreamingWebScreen(WebScreen):
230 def __init__(self, session, request):
231 WebScreen.__init__(self, session, request)
232 from Components.Sources.StreamService import StreamService
233 self["StreamService"] = StreamService(self.session.nav)
235 class M3UStreamingWebScreen(WebScreen):
236 def __init__(self, session, request):
237 WebScreen.__init__(self, session, request)
238 from Components.Sources.StaticText import StaticText
239 from Components.Sources.Config import Config
240 from Components.config import config
241 self["ref"] = StaticText()
242 self["localip"] = RequestData(request,what=RequestData.HOST)
244 class M3UStreamingCurrentServiceWebScreen(WebScreen):
245 def __init__(self, session, request):
246 WebScreen.__init__(self, session, request)
247 self["CurrentService"] = CurrentService(session)
248 self["localip"] = RequestData(request,what=RequestData.HOST)
250 class TsM3U(WebScreen):
251 def __init__(self, session, request):
252 WebScreen.__init__(self, session, request)
253 from Components.Sources.StaticText import StaticText
254 from Components.Sources.Config import Config
255 from Components.config import config
256 self["file"] = StaticText()
257 self["localip"] = RequestData(request,what=RequestData.HOST)
259 class RestartWebScreen(WebScreen):
260 def __init__(self, session, request):
261 WebScreen.__init__(self, session, request)
263 plugin.restartWebserver(session)
265 class GetPid(WebScreen):
266 def __init__(self, session, request):
267 WebScreen.__init__(self, session, request)
268 from Components.Sources.StaticText import StaticText
269 from enigma import iServiceInformation
270 pids = self.session.nav.getCurrentService()
272 pidinfo = pids.info()
273 VPID = hex(pidinfo.getInfo(iServiceInformation.sVideoPID))
274 APID = hex(pidinfo.getInfo(iServiceInformation.sAudioPID))
275 PPID = hex(pidinfo.getInfo(iServiceInformation.sPMTPID))
276 self["pids"] = StaticText("%s,%s,%s"%(PPID.lstrip("0x"),VPID.lstrip("0x"),APID.lstrip("0x")))
278 self["pids"] = StaticText("0x,0x,0x")
280 self["localip"] = RequestData(request,what=RequestData.HOST)
283 # implements the 'render'-call.
284 # this will act as a downstream_element, like a renderer.
285 class OneTimeElement(Element):
286 def __init__(self, id):
287 Element.__init__(self)
290 # CHECKME: is this ok performance-wise?
291 def handleCommand(self, args):
292 if self.source_id.find(",") >=0:
293 paramlist = self.source_id.split(",")
295 for key in paramlist:
296 arg = args.get(key, [])
300 list[key] = "".join(arg)
303 self.source.handleCommand(list)
305 for c in args.get(self.source_id, []):
306 self.source.handleCommand(c)
308 def render(self, stream):
309 t = self.source.getHTML(self.source_id)
313 self.suspended = False
316 self.suspended = True
327 class MacroElement(OneTimeElement):
328 def __init__(self, id, macro_dict, macro_name):
329 OneTimeElement.__init__(self, id)
330 self.macro_dict = macro_dict
331 self.macro_name = macro_name
333 def render(self, stream):
334 self.macro_dict[self.macro_name] = self.source.getHTML(self.source_id)
336 class StreamingElement(OneTimeElement):
337 def __init__(self, id):
338 OneTimeElement.__init__(self, id)
341 def changed(self, what):
343 self.render(self.stream)
345 def setStream(self, stream):
348 # a to-be-filled list item
350 def __init__(self, name, filternum):
352 self.filternum = filternum
355 def __init__(self, macrodict, macroname):
356 self.macrodict = macrodict
357 self.macroname = macroname
359 class TextToHTML(Converter):
360 def __init__(self, arg):
361 Converter.__init__(self, arg)
363 def getHTML(self, id):
364 return self.source.text # encode & etc. here!
366 class TextToXML(Converter):
367 def __init__(self, arg):
368 Converter.__init__(self, arg)
370 def getHTML(self, id):
371 return escape_xml(self.source.text).replace("\x19", "").replace("\x1c", "").replace("\x1e", "")
373 class TextToURL(Converter):
374 def __init__(self, arg):
375 Converter.__init__(self, arg)
377 def getHTML(self, id):
378 return self.source.text.replace(" ","%20")
380 class ReturnEmptyXML(Converter):
381 def __init__(self, arg):
382 Converter.__init__(self, arg)
384 def getHTML(self, id):
385 return "<rootElement></rootElement>"
387 # a null-output. Useful if you only want to issue a command.
388 class Null(Converter):
389 def __init__(self, arg):
390 Converter.__init__(self, arg)
392 def getHTML(self, id):
395 class JavascriptUpdate(Converter):
396 def __init__(self, arg):
397 Converter.__init__(self, arg)
399 def getHTML(self, id):
400 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
401 # all other will replace this in JS
402 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '°'))
404 # the performant 'listfiller'-engine (plfe)
405 class ListFiller(Converter):
406 def __init__(self, arg):
407 Converter.__init__(self, arg)
408 # print "ListFiller-arg: ",arg
412 lut = self.source.lut
413 conv_args = self.converter_arguments
415 # now build a ["string", 1, "string", 2]-styled list, with indices into the
416 # list to avoid lookup of item name for each entry
418 for element in conv_args:
419 if isinstance(element, basestring):
420 lutlist.append((element, None))
421 elif isinstance(element, ListItem):
422 lutlist.append((lut[element.name], element.filternum))
423 elif isinstance(element, ListMacroItem):
424 lutlist.append((element.macrodict[element.macroname], None))
426 raise Exception("neither string, ListItem nor ListMacroItem")
428 # now, for the huge list, do:
430 append = strlist.append
432 for (element, filternum) in lutlist:
436 append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
438 append(escape_xml(str(item[element])))
440 append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
442 append(quote(str(item[element])))
444 append(str(item[element]))
445 # (this will be done in c++ later!)
447 return ''.join(strlist)
449 text = property(getText)
451 class webifHandler(ContentHandler):
452 def __init__(self, session, request):
456 self.session = session
458 self.request = request
461 def start_element(self, attrs):
464 wsource = attrs["source"]
466 path = wsource.split('.')
468 scr = self.screen.getRelatedScreen(path[0])
470 print "[webif.py] Parent Screen not found!"
474 source = scr.get(path[0])
476 if isinstance(source, ObsoleteSource):
477 # however, if we found an "obsolete source", issue warning, and resolve the real source.
478 print "WARNING: WEBIF '%s' USES OBSOLETE SOURCE '%s', USE '%s' INSTEAD!" % (name, wsource, source.new_source)
479 print "OBSOLETE SOURCE WILL BE REMOVED %s, PLEASE UPDATE!" % (source.removal_date)
480 if source.description:
481 print source.description
483 wsource = source.new_source
486 # otherwise, use that source.
489 self.source_id = str(attrs.get("id", wsource))
490 self.is_streaming = "streaming" in attrs
491 self.macro_name = attrs.get("macro") or None
493 def end_element(self):
494 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
495 if not self.is_streaming:
496 if self.macro_name is None:
497 c = OneTimeElement(self.source_id)
499 c = MacroElement(self.source_id, self.macros, self.macro_name)
501 assert self.macro_name is None
502 c = StreamingElement(self.source_id)
504 c.connect(self.source)
506 self.screen.renderer.append(c)
509 def start_convert(self, attrs):
510 ctype = attrs["type"]
512 # TODO: we need something better here
513 if ctype[:4] == "web:": # for now
514 self.converter = eval(ctype[4:])
517 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
519 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
522 def end_convert(self):
523 if len(self.sub) == 1:
524 self.sub = self.sub[0]
525 c = self.converter(self.sub)
526 c.connect(self.source)
530 def parse_item(self, attrs):
532 filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4, "urlencode": 5}[attrs.get("filter", "")]
533 self.sub.append(ListItem(attrs["name"], filter))
535 assert "macro" in attrs, "e2:item must have a name= or macro= attribute!"
536 self.sub.append(ListMacroItem(self.macros, attrs["macro"]))
538 def startElement(self, name, attrs):
539 if name == "e2:screen":
540 self.screen = eval(attrs["name"])(self.session,self.request) # fixme
541 self.screens.append(self.screen)
544 if name[:3] == "e2:":
547 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
551 tag = ''.join(tag)#.encode('utf-8')
555 elif self.mode == 1: # expect "<e2:element>"
556 assert name == "e2:element", "found %s instead of e2:element" % name
557 self.start_element(attrs)
558 elif self.mode == 2: # expect "<e2:convert>"
559 if name[:3] == "e2:":
560 assert name == "e2:convert"
561 self.start_convert(attrs)
565 assert name == "e2:item", "found %s instead of e2:item!" % name
567 self.parse_item(attrs)
569 def endElement(self, name):
570 if name == "e2:screen":
574 tag = "</" + name + ">"
577 elif self.mode == 2 and name[:3] != "e2:":
579 elif self.mode == 2: # closed 'convert' -> sub
581 elif self.mode == 1: # closed 'element'
583 if name[:3] == "e2:":
586 def processingInstruction(self, target, data):
587 self.res.append('<?' + target + ' ' + data + '>')
589 def characters(self, ch):
590 ch = ch.encode('utf-8')
596 def startEntity(self, name):
597 self.res.append('&' + name + ';');
600 for screen in self.screens:
604 print "screen cleanup!"
605 for screen in self.screens:
610 def renderPage(stream, path, req, session):
612 # read in the template, create required screens
613 # we don't have persistense yet.
614 # if we had, this first part would only be done once.
615 handler = webifHandler(session,req)
616 parser = make_parser()
617 parser.setFeature(feature_namespaces, 0)
618 parser.setContentHandler(handler)
619 parser.parse(open(util.sibpath(__file__, path)))
621 # by default, we have non-streaming pages
624 # first, apply "commands" (aka. URL argument)
625 for x in handler.res:
626 if isinstance(x, Element):
627 x.handleCommand(req.args)
631 # now, we have a list with static texts mixed
632 # with non-static Elements.
633 # flatten this list, write into the stream.
634 for x in handler.res:
635 if isinstance(x, Element):
636 if isinstance(x, StreamingElement):
644 from twisted.internet import reactor
646 reactor.callLater(3, ping, s)
648 # if we met a "StreamingElement", there is at least one
649 # element which wants to output data more than once,
650 # i.e. on host-originated changes.
651 # in this case, don't finish yet, don't cleanup yet,
652 # but instead do that when the client disconnects.
654 streamFinish(handler, stream)
657 # you *need* something which constantly sends something in a regular interval,
658 # in order to detect disconnected clients.
659 # i agree that this "ping" sucks terrible, so better be sure to have something
660 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
662 stream.closed_callback = lambda : streamFinish(handler, stream)
664 def streamFinish(handler, stream):