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
42 from WebComponents.Sources.LocationsAndTags import LocationsAndTags
44 from Components.Sources.FrontendStatus import FrontendStatus
46 from Components.Converter.Converter import Converter
48 from Components.Element import Element
50 from xml.sax import make_parser
51 from xml.sax.handler import ContentHandler, feature_namespaces
52 from xml.sax.saxutils import escape as escape_xml
53 from twisted.python import util
54 from urllib2 import quote
56 # prototype of the new web frontend template system.
58 class WebScreen(Screen):
59 def __init__(self, session, request):
60 Screen.__init__(self, session)
61 self.stand_alone = True
62 self.request = request
65 class DummyWebScreen(WebScreen):
66 #use it, if you dont need any source, just to can do a static file with an xml-file
67 def __init__(self, session, request):
68 WebScreen.__init__(self, session, request)
70 class UpdateWebScreen(WebScreen):
71 def __init__(self, session, request):
72 WebScreen.__init__(self, session, request)
73 self["CurrentTime"] = Clock()
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 LocationsAndTagsWebScreen(WebScreen):
140 def __init__(self, session, request):
141 WebScreen.__init__(self, session, request)
142 self["CurrentLocation"] = LocationsAndTags(session,LocationsAndTags.CURRLOCATION)
143 self["Locations"] = LocationsAndTags(session,LocationsAndTags.LOCATIONS)
144 self["Tags"] = LocationsAndTags(session,LocationsAndTags.TAGS)
146 class EPGWebScreen(WebScreen):
147 def __init__(self, session, request):
148 WebScreen.__init__(self, session, request)
150 self["EPGTITLE"] = EPG(session,func=EPG.TITLE)
151 self["EPGSERVICE"] = EPG(session,func=EPG.SERVICE)
152 self["EPGBOUQUETNOW"] = EPG(session,func=EPG.BOUQUETNOW)
153 self["EPGBOUQUETNEXT"] = EPG(session,func=EPG.BOUQUETNEXT)
154 self["EPGSERVICENOW"] = EPG(session,func=EPG.SERVICENOW)
155 self["EPGSERVICENEXT"] = EPG(session,func=EPG.SERVICENEXT)
156 self["EPGBOUQUET"] = EPG(session,func=EPG.BOUQUET)
158 def getServiceList(self, sRef):
159 self["ServiceList"].root = sRef
161 class MovieWebScreen(WebScreen):
162 def __init__(self, session, request):
163 WebScreen.__init__(self, session, request)
164 from Components.MovieList import MovieList
165 from Tools.Directories import resolveFilename,SCOPE_HDD
166 movielist = MovieList(eServiceReference("2:0:1:0:0:0:0:0:0:0:" + resolveFilename(SCOPE_HDD)))
167 self["MovieList"] = Movie(session,movielist,func = Movie.LIST)
168 self["MovieFileDel"] = Movie(session,movielist,func = Movie.DEL)
169 self["MovieTags"] = Movie(session,movielist,func = Movie.TAGS)
170 self["localip"] = RequestData(request,what=RequestData.HOST)
172 class MediaPlayerWebScreen(WebScreen):
173 def __init__(self, session, request):
174 WebScreen.__init__(self, session, request)
175 self["FileList"] = MP(session,func = MP.LIST)
176 self["PlayFile"] = MP(session,func = MP.PLAY)
177 self["Command"] = MP(session,func = MP.COMMAND)
178 self["WritePlaylist"] = MP(session,func = MP.WRITEPLAYLIST)
180 class AutoTimerWebScreen(WebScreen):
181 def __init__(self, session, request):
182 WebScreen.__init__(self, session, request)
183 self["AutoTimerList"] = AT(session,func = AT.LIST)
184 self["AutoTimerWrite"] = AT(session,func = AT.WRITE)
186 class TimerWebScreen(WebScreen):
187 def __init__(self, session, request):
188 WebScreen.__init__(self, session, request)
189 self["TimerList"] = Timer(session,func = Timer.LIST)
190 self["TimerAddEventID"] = Timer(session,func = Timer.ADDBYID)
191 self["TimerAdd"] = Timer(session,func = Timer.ADD)
192 self["TimerDel"] = Timer(session,func = Timer.DEL)
193 self["TimerChange"] = Timer(session,func = Timer.CHANGE)
194 self["TimerListWrite"] = Timer(session,func = Timer.WRITE)
195 self["TVBrowser"] = Timer(session,func = Timer.TVBROWSER)
196 self["RecordNow"] = Timer(session,func = Timer.RECNOW)
198 class RemoteWebScreen(WebScreen):
199 def __init__(self, session, request):
200 WebScreen.__init__(self, session, request)
201 self["RemoteControl"] = RemoteControl(session)
203 class PowerWebScreen(WebScreen):
204 def __init__(self, session, request):
205 WebScreen.__init__(self, session, request)
206 self["PowerState"] = PowerState(session)
208 class ParentControlWebScreen(WebScreen):
209 def __init__(self, session, request):
210 WebScreen.__init__(self, session, request)
211 self["ParentControlList"] = ParentControl(session)
213 class WAPWebScreen(WebScreen):
214 def __init__(self, session, request):
215 WebScreen.__init__(self, session, request)
216 self["WAPFillOptionListYear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
217 self["WAPFillOptionListDay"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
218 self["WAPFillOptionListMonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
219 self["WAPFillOptionListShour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
220 self["WAPFillOptionListSmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
221 self["WAPFillOptionListEhour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
222 self["WAPFillOptionListEmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
224 self["WAPFillOptionListRecord"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
225 self["WAPFillOptionListAfterEvent"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
227 self["WAPFillValueName"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
228 self["WAPFillValueDescr"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
229 self["WAPFillLocation"] = WAPfunctions(session,func = WAPfunctions.LOCATIONLIST)
230 self["WAPFillTags"] = WAPfunctions(session,func = WAPfunctions.TAGLIST)
232 self["WAPFillOptionListRepeated"] = WAPfunctions(session,func = WAPfunctions.REPEATED)
233 self["WAPServiceList"] = WAPfunctions(session, func = WAPfunctions.SERVICELIST)
235 self["WAPdeleteOldOnSave"] = WAPfunctions(session,func = WAPfunctions.DELETEOLD)
237 class StreamingWebScreen(WebScreen):
238 def __init__(self, session, request):
239 WebScreen.__init__(self, session, request)
240 from Components.Sources.StreamService import StreamService
241 self["StreamService"] = StreamService(self.session.nav)
243 class M3UStreamingWebScreen(WebScreen):
244 def __init__(self, session, request):
245 WebScreen.__init__(self, session, request)
246 from Components.Sources.StaticText import StaticText
247 from Components.Sources.Config import Config
248 from Components.config import config
249 self["ref"] = StaticText()
250 self["localip"] = RequestData(request,what=RequestData.HOST)
252 class M3UStreamingCurrentServiceWebScreen(WebScreen):
253 def __init__(self, session, request):
254 WebScreen.__init__(self, session, request)
255 self["CurrentService"] = CurrentService(session)
256 self["localip"] = RequestData(request,what=RequestData.HOST)
258 class TsM3U(WebScreen):
259 def __init__(self, session, request):
260 WebScreen.__init__(self, session, request)
261 from Components.Sources.StaticText import StaticText
262 from Components.Sources.Config import Config
263 from Components.config import config
264 self["file"] = StaticText()
265 self["localip"] = RequestData(request,what=RequestData.HOST)
267 class RestartWebScreen(WebScreen):
268 def __init__(self, session, request):
269 WebScreen.__init__(self, session, request)
271 plugin.restartWebserver(session)
273 class GetPid(WebScreen):
274 def __init__(self, session, request):
275 WebScreen.__init__(self, session, request)
276 from Components.Sources.StaticText import StaticText
277 from enigma import iServiceInformation
278 pids = self.session.nav.getCurrentService()
280 pidinfo = pids.info()
281 VPID = hex(pidinfo.getInfo(iServiceInformation.sVideoPID))
282 APID = hex(pidinfo.getInfo(iServiceInformation.sAudioPID))
283 PPID = hex(pidinfo.getInfo(iServiceInformation.sPMTPID))
284 self["pids"] = StaticText("%s,%s,%s"%(PPID.lstrip("0x"),VPID.lstrip("0x"),APID.lstrip("0x")))
286 self["pids"] = StaticText("0x,0x,0x")
288 self["localip"] = RequestData(request,what=RequestData.HOST)
290 class About2(WebScreen):
291 def __init__(self, session, request):
292 WebScreen.__init__(self, session, request)
293 from WebComponents.Sources.Network import Network
294 from WebComponents.Sources.Hdd import Hdd
295 self["network"] = Network()
298 # implements the 'render'-call.
299 # this will act as a downstream_element, like a renderer.
300 class OneTimeElement(Element):
301 def __init__(self, id):
302 Element.__init__(self)
305 # CHECKME: is this ok performance-wise?
306 def handleCommand(self, args):
307 if self.source_id.find(",") >=0:
308 paramlist = self.source_id.split(",")
310 for key in paramlist:
311 arg = args.get(key, [])
315 list[key] = "".join(arg)
318 self.source.handleCommand(list)
320 for c in args.get(self.source_id, []):
321 self.source.handleCommand(c)
323 def render(self, stream):
324 t = self.source.getHTML(self.source_id)
328 self.suspended = False
331 self.suspended = True
342 class MacroElement(OneTimeElement):
343 def __init__(self, id, macro_dict, macro_name):
344 OneTimeElement.__init__(self, id)
345 self.macro_dict = macro_dict
346 self.macro_name = macro_name
348 def render(self, stream):
349 self.macro_dict[self.macro_name] = self.source.getHTML(self.source_id)
351 class StreamingElement(OneTimeElement):
352 def __init__(self, id):
353 OneTimeElement.__init__(self, id)
356 def changed(self, what):
358 self.render(self.stream)
360 def setStream(self, stream):
363 # a to-be-filled list item
365 def __init__(self, name, filternum):
367 self.filternum = filternum
370 def __init__(self, macrodict, macroname):
371 self.macrodict = macrodict
372 self.macroname = macroname
374 class TextToHTML(Converter):
375 def __init__(self, arg):
376 Converter.__init__(self, arg)
378 def getHTML(self, id):
379 return self.source.text # encode & etc. here!
381 class TextToXML(Converter):
382 def __init__(self, arg):
383 Converter.__init__(self, arg)
385 def getHTML(self, id):
386 return escape_xml(self.source.text).replace("\x19", "").replace("\x1c", "").replace("\x1e", "")
388 class TextToURL(Converter):
389 def __init__(self, arg):
390 Converter.__init__(self, arg)
392 def getHTML(self, id):
393 return self.source.text.replace(" ","%20")
395 class ReturnEmptyXML(Converter):
396 def __init__(self, arg):
397 Converter.__init__(self, arg)
399 def getHTML(self, id):
400 return "<rootElement></rootElement>"
402 # a null-output. Useful if you only want to issue a command.
403 class Null(Converter):
404 def __init__(self, arg):
405 Converter.__init__(self, arg)
407 def getHTML(self, id):
410 class JavascriptUpdate(Converter):
411 def __init__(self, arg):
412 Converter.__init__(self, arg)
414 def getHTML(self, id):
415 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
416 # all other will replace this in JS
417 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '°'))
419 # the performant 'listfiller'-engine (plfe)
420 class ListFiller(Converter):
421 def __init__(self, arg):
422 Converter.__init__(self, arg)
423 # print "ListFiller-arg: ",arg
427 lut = self.source.lut
428 conv_args = self.converter_arguments
430 # now build a ["string", 1, "string", 2]-styled list, with indices into the
431 # list to avoid lookup of item name for each entry
433 for element in conv_args:
434 if isinstance(element, basestring):
435 lutlist.append((element, None))
436 elif isinstance(element, ListItem):
437 lutlist.append((lut[element.name], element.filternum))
438 elif isinstance(element, ListMacroItem):
439 lutlist.append((element.macrodict[element.macroname], None))
441 raise Exception("neither string, ListItem nor ListMacroItem")
443 # now, for the huge list, do:
445 append = strlist.append
447 for (element, filternum) in lutlist:
451 append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
453 append(escape_xml(str(item[element])))
455 append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
457 append(quote(str(item[element])))
459 append(str(item[element]))
460 # (this will be done in c++ later!)
462 return ''.join(strlist)
464 text = property(getText)
466 class webifHandler(ContentHandler):
467 def __init__(self, session, request):
471 self.session = session
473 self.request = request
476 def start_element(self, attrs):
479 wsource = attrs["source"]
481 path = wsource.split('.')
483 scr = self.screen.getRelatedScreen(path[0])
485 print "[webif.py] Parent Screen not found!"
489 source = scr.get(path[0])
491 if isinstance(source, ObsoleteSource):
492 # however, if we found an "obsolete source", issue warning, and resolve the real source.
493 print "WARNING: WEBIF '%s' USES OBSOLETE SOURCE '%s', USE '%s' INSTEAD!" % (name, wsource, source.new_source)
494 print "OBSOLETE SOURCE WILL BE REMOVED %s, PLEASE UPDATE!" % (source.removal_date)
495 if source.description:
496 print source.description
498 wsource = source.new_source
501 # otherwise, use that source.
504 self.source_id = str(attrs.get("id", wsource))
505 self.is_streaming = "streaming" in attrs
506 self.macro_name = attrs.get("macro") or None
508 def end_element(self):
509 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
510 if not self.is_streaming:
511 if self.macro_name is None:
512 c = OneTimeElement(self.source_id)
514 c = MacroElement(self.source_id, self.macros, self.macro_name)
516 assert self.macro_name is None
517 c = StreamingElement(self.source_id)
519 c.connect(self.source)
521 self.screen.renderer.append(c)
524 def start_convert(self, attrs):
525 ctype = attrs["type"]
527 # TODO: we need something better here
528 if ctype[:4] == "web:": # for now
529 self.converter = eval(ctype[4:])
532 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
534 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
537 def end_convert(self):
538 if len(self.sub) == 1:
539 self.sub = self.sub[0]
540 c = self.converter(self.sub)
541 c.connect(self.source)
545 def parse_item(self, attrs):
547 filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4, "urlencode": 5}[attrs.get("filter", "")]
548 self.sub.append(ListItem(attrs["name"], filter))
550 assert "macro" in attrs, "e2:item must have a name= or macro= attribute!"
551 self.sub.append(ListMacroItem(self.macros, attrs["macro"]))
553 def startElement(self, name, attrs):
554 if name == "e2:screen":
555 self.screen = eval(attrs["name"])(self.session,self.request) # fixme
556 self.screens.append(self.screen)
559 if name[:3] == "e2:":
562 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
566 tag = ''.join(tag)#.encode('utf-8')
570 elif self.mode == 1: # expect "<e2:element>"
571 assert name == "e2:element", "found %s instead of e2:element" % name
572 self.start_element(attrs)
573 elif self.mode == 2: # expect "<e2:convert>"
574 if name[:3] == "e2:":
575 assert name == "e2:convert"
576 self.start_convert(attrs)
580 assert name == "e2:item", "found %s instead of e2:item!" % name
582 self.parse_item(attrs)
584 def endElement(self, name):
585 if name == "e2:screen":
589 tag = "</" + name + ">"
592 elif self.mode == 2 and name[:3] != "e2:":
594 elif self.mode == 2: # closed 'convert' -> sub
596 elif self.mode == 1: # closed 'element'
598 if name[:3] == "e2:":
601 def processingInstruction(self, target, data):
602 self.res.append('<?' + target + ' ' + data + '>')
604 def characters(self, ch):
605 ch = ch.encode('utf-8')
611 def startEntity(self, name):
612 self.res.append('&' + name + ';');
615 for screen in self.screens:
619 print "screen cleanup!"
620 for screen in self.screens:
625 def renderPage(stream, path, req, session):
627 # read in the template, create required screens
628 # we don't have persistense yet.
629 # if we had, this first part would only be done once.
630 handler = webifHandler(session,req)
631 parser = make_parser()
632 parser.setFeature(feature_namespaces, 0)
633 parser.setContentHandler(handler)
634 parser.parse(open(util.sibpath(__file__, path)))
636 # by default, we have non-streaming pages
639 # first, apply "commands" (aka. URL argument)
640 for x in handler.res:
641 if isinstance(x, Element):
642 x.handleCommand(req.args)
646 # now, we have a list with static texts mixed
647 # with non-static Elements.
648 # flatten this list, write into the stream.
649 for x in handler.res:
650 if isinstance(x, Element):
651 if isinstance(x, StreamingElement):
659 from twisted.internet import reactor
661 reactor.callLater(3, ping, s)
663 # if we met a "StreamingElement", there is at least one
664 # element which wants to output data more than once,
665 # i.e. on host-originated changes.
666 # in this case, don't finish yet, don't cleanup yet,
667 # but instead do that when the client disconnects.
669 streamFinish(handler, stream)
672 # you *need* something which constantly sends something in a regular interval,
673 # in order to detect disconnected clients.
674 # i agree that this "ping" sucks terrible, so better be sure to have something
675 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
677 stream.closed_callback = lambda : streamFinish(handler, stream)
679 def streamFinish(handler, stream):