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 from WebComponents.Sources.Frontend import Frontend
296 self["Network"] = Network()
298 self["Frontends"] = Frontend()
300 # implements the 'render'-call.
301 # this will act as a downstream_element, like a renderer.
302 class OneTimeElement(Element):
303 def __init__(self, id):
304 Element.__init__(self)
307 # CHECKME: is this ok performance-wise?
308 def handleCommand(self, args):
309 if self.source_id.find(",") >=0:
310 paramlist = self.source_id.split(",")
312 for key in paramlist:
313 arg = args.get(key, [])
317 list[key] = "".join(arg)
320 self.source.handleCommand(list)
322 for c in args.get(self.source_id, []):
323 self.source.handleCommand(c)
325 def render(self, stream):
326 t = self.source.getHTML(self.source_id)
330 self.suspended = False
333 self.suspended = True
344 class MacroElement(OneTimeElement):
345 def __init__(self, id, macro_dict, macro_name):
346 OneTimeElement.__init__(self, id)
347 self.macro_dict = macro_dict
348 self.macro_name = macro_name
350 def render(self, stream):
351 self.macro_dict[self.macro_name] = self.source.getHTML(self.source_id)
353 class StreamingElement(OneTimeElement):
354 def __init__(self, id):
355 OneTimeElement.__init__(self, id)
358 def changed(self, what):
360 self.render(self.stream)
362 def setStream(self, stream):
365 # a to-be-filled list item
367 def __init__(self, name, filternum):
369 self.filternum = filternum
372 def __init__(self, macrodict, macroname):
373 self.macrodict = macrodict
374 self.macroname = macroname
376 class TextToHTML(Converter):
377 def __init__(self, arg):
378 Converter.__init__(self, arg)
380 def getHTML(self, id):
381 return self.source.text # encode & etc. here!
383 class TextToXML(Converter):
384 def __init__(self, arg):
385 Converter.__init__(self, arg)
387 def getHTML(self, id):
388 return escape_xml(self.source.text).replace("\x19", "").replace("\x1c", "").replace("\x1e", "")
390 class TextToURL(Converter):
391 def __init__(self, arg):
392 Converter.__init__(self, arg)
394 def getHTML(self, id):
395 return self.source.text.replace(" ","%20")
397 class ReturnEmptyXML(Converter):
398 def __init__(self, arg):
399 Converter.__init__(self, arg)
401 def getHTML(self, id):
402 return "<rootElement></rootElement>"
404 # a null-output. Useful if you only want to issue a command.
405 class Null(Converter):
406 def __init__(self, arg):
407 Converter.__init__(self, arg)
409 def getHTML(self, id):
412 class JavascriptUpdate(Converter):
413 def __init__(self, arg):
414 Converter.__init__(self, arg)
416 def getHTML(self, id):
417 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
418 # all other will replace this in JS
419 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '°'))
421 # the performant 'listfiller'-engine (plfe)
422 class ListFiller(Converter):
423 def __init__(self, arg):
424 Converter.__init__(self, arg)
425 # print "ListFiller-arg: ",arg
429 lut = self.source.lut
430 conv_args = self.converter_arguments
432 # now build a ["string", 1, "string", 2]-styled list, with indices into the
433 # list to avoid lookup of item name for each entry
435 for element in conv_args:
436 if isinstance(element, basestring):
437 lutlist.append((element, None))
438 elif isinstance(element, ListItem):
439 lutlist.append((lut[element.name], element.filternum))
440 elif isinstance(element, ListMacroItem):
441 lutlist.append((element.macrodict[element.macroname], None))
443 raise Exception("neither string, ListItem nor ListMacroItem")
445 # now, for the huge list, do:
447 append = strlist.append
449 for (element, filternum) in lutlist:
453 append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
455 append(escape_xml(str(item[element])))
457 append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
459 append(quote(str(item[element])))
461 append(str(item[element]))
462 # (this will be done in c++ later!)
464 return ''.join(strlist)
466 text = property(getText)
468 class webifHandler(ContentHandler):
469 def __init__(self, session, request):
473 self.session = session
475 self.request = request
478 def start_element(self, attrs):
481 wsource = attrs["source"]
483 path = wsource.split('.')
485 scr = self.screen.getRelatedScreen(path[0])
487 print "[webif.py] Parent Screen not found!"
491 source = scr.get(path[0])
493 if isinstance(source, ObsoleteSource):
494 # however, if we found an "obsolete source", issue warning, and resolve the real source.
495 print "WARNING: WEBIF '%s' USES OBSOLETE SOURCE '%s', USE '%s' INSTEAD!" % (name, wsource, source.new_source)
496 print "OBSOLETE SOURCE WILL BE REMOVED %s, PLEASE UPDATE!" % (source.removal_date)
497 if source.description:
498 print source.description
500 wsource = source.new_source
503 # otherwise, use that source.
506 self.source_id = str(attrs.get("id", wsource))
507 self.is_streaming = "streaming" in attrs
508 self.macro_name = attrs.get("macro") or None
510 def end_element(self):
511 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
512 if not self.is_streaming:
513 if self.macro_name is None:
514 c = OneTimeElement(self.source_id)
516 c = MacroElement(self.source_id, self.macros, self.macro_name)
518 assert self.macro_name is None
519 c = StreamingElement(self.source_id)
521 c.connect(self.source)
523 self.screen.renderer.append(c)
526 def start_convert(self, attrs):
527 ctype = attrs["type"]
529 # TODO: we need something better here
530 if ctype[:4] == "web:": # for now
531 self.converter = eval(ctype[4:])
534 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
536 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
539 def end_convert(self):
540 if len(self.sub) == 1:
541 self.sub = self.sub[0]
542 c = self.converter(self.sub)
543 c.connect(self.source)
547 def parse_item(self, attrs):
549 filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4, "urlencode": 5}[attrs.get("filter", "")]
550 self.sub.append(ListItem(attrs["name"], filter))
552 assert "macro" in attrs, "e2:item must have a name= or macro= attribute!"
553 self.sub.append(ListMacroItem(self.macros, attrs["macro"]))
555 def startElement(self, name, attrs):
556 if name == "e2:screen":
557 self.screen = eval(attrs["name"])(self.session,self.request) # fixme
558 self.screens.append(self.screen)
561 if name[:3] == "e2:":
564 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
568 tag = ''.join(tag)#.encode('utf-8')
572 elif self.mode == 1: # expect "<e2:element>"
573 assert name == "e2:element", "found %s instead of e2:element" % name
574 self.start_element(attrs)
575 elif self.mode == 2: # expect "<e2:convert>"
576 if name[:3] == "e2:":
577 assert name == "e2:convert"
578 self.start_convert(attrs)
582 assert name == "e2:item", "found %s instead of e2:item!" % name
584 self.parse_item(attrs)
586 def endElement(self, name):
587 if name == "e2:screen":
591 tag = "</" + name + ">"
594 elif self.mode == 2 and name[:3] != "e2:":
596 elif self.mode == 2: # closed 'convert' -> sub
598 elif self.mode == 1: # closed 'element'
600 if name[:3] == "e2:":
603 def processingInstruction(self, target, data):
604 self.res.append('<?' + target + ' ' + data + '>')
606 def characters(self, ch):
607 ch = ch.encode('utf-8')
613 def startEntity(self, name):
614 self.res.append('&' + name + ';');
617 for screen in self.screens:
621 print "screen cleanup!"
622 for screen in self.screens:
627 def renderPage(stream, path, req, session):
628 # read in the template, create required screens
629 # we don't have persistense yet.
630 # if we had, this first part would only be done once.
631 handler = webifHandler(session,req)
632 parser = make_parser()
633 parser.setFeature(feature_namespaces, 0)
634 parser.setContentHandler(handler)
635 parser.parse(open(util.sibpath(__file__, path)))
637 # by default, we have non-streaming pages
640 # first, apply "commands" (aka. URL argument)
641 for x in handler.res:
642 if isinstance(x, Element):
643 x.handleCommand(req.args)
647 # now, we have a list with static texts mixed
648 # with non-static Elements.
649 # flatten this list, write into the stream.
650 for x in handler.res:
651 if isinstance(x, Element):
652 if isinstance(x, StreamingElement):
660 from twisted.internet import reactor
662 reactor.callLater(3, ping, s)
664 # if we met a "StreamingElement", there is at least one
665 # element which wants to output data more than once,
666 # i.e. on host-originated changes.
667 # in this case, don't finish yet, don't cleanup yet,
668 # but instead do that when the client disconnects.
670 streamFinish(handler, stream)
673 # you *need* something which constantly sends something in a regular interval,
674 # in order to detect disconnected clients.
675 # i agree that this "ping" sucks terrible, so better be sure to have something
676 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
678 stream.closed_callback = lambda : streamFinish(handler, stream)
680 def streamFinish(handler, stream):