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["Locations"] = LocationsAndTags(session,LocationsAndTags.LOCATIONS)
143 self["Tags"] = LocationsAndTags(session,LocationsAndTags.TAGS)
145 class EPGWebScreen(WebScreen):
146 def __init__(self, session, request):
147 WebScreen.__init__(self, session, request)
149 self["EPGTITLE"] = EPG(session,func=EPG.TITLE)
150 self["EPGSERVICE"] = EPG(session,func=EPG.SERVICE)
151 self["EPGNOW"] = EPG(session,func=EPG.NOW)
152 self["EPGNEXT"] = EPG(session,func=EPG.NEXT)
153 self["EPGBOUQUET"] = EPG(session,func=EPG.BOUQUET)
155 def getServiceList(self, sRef):
156 self["ServiceList"].root = sRef
158 class MovieWebScreen(WebScreen):
159 def __init__(self, session, request):
160 WebScreen.__init__(self, session, request)
161 from Components.MovieList import MovieList
162 from Tools.Directories import resolveFilename,SCOPE_HDD
163 movielist = MovieList(eServiceReference("2:0:1:0:0:0:0:0:0:0:" + resolveFilename(SCOPE_HDD)))
164 self["MovieList"] = Movie(session,movielist,func = Movie.LIST)
165 self["MovieFileDel"] = Movie(session,movielist,func = Movie.DEL)
166 self["MovieTags"] = Movie(session,movielist,func = Movie.TAGS)
167 self["localip"] = RequestData(request,what=RequestData.HOST)
169 class MediaPlayerWebScreen(WebScreen):
170 def __init__(self, session, request):
171 WebScreen.__init__(self, session, request)
172 self["FileList"] = MP(session,func = MP.LIST)
173 self["PlayFile"] = MP(session,func = MP.PLAY)
174 self["Command"] = MP(session,func = MP.COMMAND)
175 self["WritePlaylist"] = MP(session,func = MP.WRITEPLAYLIST)
177 class AutoTimerWebScreen(WebScreen):
178 def __init__(self, session, request):
179 WebScreen.__init__(self, session, request)
180 self["AutoTimerList"] = AT(session,func = AT.LIST)
181 self["AutoTimerWrite"] = AT(session,func = AT.WRITE)
183 class TimerWebScreen(WebScreen):
184 def __init__(self, session, request):
185 WebScreen.__init__(self, session, request)
186 self["TimerList"] = Timer(session,func = Timer.LIST)
187 self["TimerAddEventID"] = Timer(session,func = Timer.ADDBYID)
188 self["TimerAdd"] = Timer(session,func = Timer.ADD)
189 self["TimerDel"] = Timer(session,func = Timer.DEL)
190 self["TimerChange"] = Timer(session,func = Timer.CHANGE)
191 self["TimerListWrite"] = Timer(session,func = Timer.WRITE)
192 self["TVBrowser"] = Timer(session,func = Timer.TVBROWSER)
193 self["RecordNow"] = Timer(session,func = Timer.RECNOW)
195 class RemoteWebScreen(WebScreen):
196 def __init__(self, session, request):
197 WebScreen.__init__(self, session, request)
198 self["RemoteControl"] = RemoteControl(session)
200 class PowerWebScreen(WebScreen):
201 def __init__(self, session, request):
202 WebScreen.__init__(self, session, request)
203 self["PowerState"] = PowerState(session)
205 class ParentControlWebScreen(WebScreen):
206 def __init__(self, session, request):
207 WebScreen.__init__(self, session, request)
208 self["ParentControlList"] = ParentControl(session)
210 class WAPWebScreen(WebScreen):
211 def __init__(self, session, request):
212 WebScreen.__init__(self, session, request)
213 self["WAPFillOptionListYear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
214 self["WAPFillOptionListDay"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
215 self["WAPFillOptionListMonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
216 self["WAPFillOptionListShour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
217 self["WAPFillOptionListSmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
218 self["WAPFillOptionListEhour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
219 self["WAPFillOptionListEmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
221 self["WAPFillOptionListRecord"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
222 self["WAPFillOptionListAfterEvent"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
224 self["WAPFillValueName"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
225 self["WAPFillValueDescr"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
226 self["WAPFillLocation"] = WAPfunctions(session,func = WAPfunctions.LOCATIONLIST)
227 self["WAPFillTags"] = WAPfunctions(session,func = WAPfunctions.TAGLIST)
229 self["WAPFillOptionListRepeated"] = WAPfunctions(session,func = WAPfunctions.REPEATED)
230 self["WAPServiceList"] = WAPfunctions(session, func = WAPfunctions.SERVICELIST)
232 self["WAPdeleteOldOnSave"] = WAPfunctions(session,func = WAPfunctions.DELETEOLD)
234 class StreamingWebScreen(WebScreen):
235 def __init__(self, session, request):
236 WebScreen.__init__(self, session, request)
237 from Components.Sources.StreamService import StreamService
238 self["StreamService"] = StreamService(self.session.nav)
240 class M3UStreamingWebScreen(WebScreen):
241 def __init__(self, session, request):
242 WebScreen.__init__(self, session, request)
243 from Components.Sources.StaticText import StaticText
244 from Components.Sources.Config import Config
245 from Components.config import config
246 self["ref"] = StaticText()
247 self["localip"] = RequestData(request,what=RequestData.HOST)
249 class M3UStreamingCurrentServiceWebScreen(WebScreen):
250 def __init__(self, session, request):
251 WebScreen.__init__(self, session, request)
252 self["CurrentService"] = CurrentService(session)
253 self["localip"] = RequestData(request,what=RequestData.HOST)
255 class TsM3U(WebScreen):
256 def __init__(self, session, request):
257 WebScreen.__init__(self, session, request)
258 from Components.Sources.StaticText import StaticText
259 from Components.Sources.Config import Config
260 from Components.config import config
261 self["file"] = StaticText()
262 self["localip"] = RequestData(request,what=RequestData.HOST)
264 class RestartWebScreen(WebScreen):
265 def __init__(self, session, request):
266 WebScreen.__init__(self, session, request)
268 plugin.restartWebserver(session)
270 class GetPid(WebScreen):
271 def __init__(self, session, request):
272 WebScreen.__init__(self, session, request)
273 from Components.Sources.StaticText import StaticText
274 from enigma import iServiceInformation
275 pids = self.session.nav.getCurrentService()
277 pidinfo = pids.info()
278 VPID = hex(pidinfo.getInfo(iServiceInformation.sVideoPID))
279 APID = hex(pidinfo.getInfo(iServiceInformation.sAudioPID))
280 PPID = hex(pidinfo.getInfo(iServiceInformation.sPMTPID))
281 self["pids"] = StaticText("%s,%s,%s"%(PPID.lstrip("0x"),VPID.lstrip("0x"),APID.lstrip("0x")))
283 self["pids"] = StaticText("0x,0x,0x")
285 self["localip"] = RequestData(request,what=RequestData.HOST)
288 # implements the 'render'-call.
289 # this will act as a downstream_element, like a renderer.
290 class OneTimeElement(Element):
291 def __init__(self, id):
292 Element.__init__(self)
295 # CHECKME: is this ok performance-wise?
296 def handleCommand(self, args):
297 if self.source_id.find(",") >=0:
298 paramlist = self.source_id.split(",")
300 for key in paramlist:
301 arg = args.get(key, [])
305 list[key] = "".join(arg)
308 self.source.handleCommand(list)
310 for c in args.get(self.source_id, []):
311 self.source.handleCommand(c)
313 def render(self, stream):
314 t = self.source.getHTML(self.source_id)
318 self.suspended = False
321 self.suspended = True
332 class MacroElement(OneTimeElement):
333 def __init__(self, id, macro_dict, macro_name):
334 OneTimeElement.__init__(self, id)
335 self.macro_dict = macro_dict
336 self.macro_name = macro_name
338 def render(self, stream):
339 self.macro_dict[self.macro_name] = self.source.getHTML(self.source_id)
341 class StreamingElement(OneTimeElement):
342 def __init__(self, id):
343 OneTimeElement.__init__(self, id)
346 def changed(self, what):
348 self.render(self.stream)
350 def setStream(self, stream):
353 # a to-be-filled list item
355 def __init__(self, name, filternum):
357 self.filternum = filternum
360 def __init__(self, macrodict, macroname):
361 self.macrodict = macrodict
362 self.macroname = macroname
364 class TextToHTML(Converter):
365 def __init__(self, arg):
366 Converter.__init__(self, arg)
368 def getHTML(self, id):
369 return self.source.text # encode & etc. here!
371 class TextToXML(Converter):
372 def __init__(self, arg):
373 Converter.__init__(self, arg)
375 def getHTML(self, id):
376 return escape_xml(self.source.text).replace("\x19", "").replace("\x1c", "").replace("\x1e", "")
378 class TextToURL(Converter):
379 def __init__(self, arg):
380 Converter.__init__(self, arg)
382 def getHTML(self, id):
383 return self.source.text.replace(" ","%20")
385 class ReturnEmptyXML(Converter):
386 def __init__(self, arg):
387 Converter.__init__(self, arg)
389 def getHTML(self, id):
390 return "<rootElement></rootElement>"
392 # a null-output. Useful if you only want to issue a command.
393 class Null(Converter):
394 def __init__(self, arg):
395 Converter.__init__(self, arg)
397 def getHTML(self, id):
400 class JavascriptUpdate(Converter):
401 def __init__(self, arg):
402 Converter.__init__(self, arg)
404 def getHTML(self, id):
405 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
406 # all other will replace this in JS
407 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '°'))
409 # the performant 'listfiller'-engine (plfe)
410 class ListFiller(Converter):
411 def __init__(self, arg):
412 Converter.__init__(self, arg)
413 # print "ListFiller-arg: ",arg
417 lut = self.source.lut
418 conv_args = self.converter_arguments
420 # now build a ["string", 1, "string", 2]-styled list, with indices into the
421 # list to avoid lookup of item name for each entry
423 for element in conv_args:
424 if isinstance(element, basestring):
425 lutlist.append((element, None))
426 elif isinstance(element, ListItem):
427 lutlist.append((lut[element.name], element.filternum))
428 elif isinstance(element, ListMacroItem):
429 lutlist.append((element.macrodict[element.macroname], None))
431 raise Exception("neither string, ListItem nor ListMacroItem")
433 # now, for the huge list, do:
435 append = strlist.append
437 for (element, filternum) in lutlist:
441 append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
443 append(escape_xml(str(item[element])))
445 append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
447 append(quote(str(item[element])))
449 append(str(item[element]))
450 # (this will be done in c++ later!)
452 return ''.join(strlist)
454 text = property(getText)
456 class webifHandler(ContentHandler):
457 def __init__(self, session, request):
461 self.session = session
463 self.request = request
466 def start_element(self, attrs):
469 wsource = attrs["source"]
471 path = wsource.split('.')
473 scr = self.screen.getRelatedScreen(path[0])
475 print "[webif.py] Parent Screen not found!"
479 source = scr.get(path[0])
481 if isinstance(source, ObsoleteSource):
482 # however, if we found an "obsolete source", issue warning, and resolve the real source.
483 print "WARNING: WEBIF '%s' USES OBSOLETE SOURCE '%s', USE '%s' INSTEAD!" % (name, wsource, source.new_source)
484 print "OBSOLETE SOURCE WILL BE REMOVED %s, PLEASE UPDATE!" % (source.removal_date)
485 if source.description:
486 print source.description
488 wsource = source.new_source
491 # otherwise, use that source.
494 self.source_id = str(attrs.get("id", wsource))
495 self.is_streaming = "streaming" in attrs
496 self.macro_name = attrs.get("macro") or None
498 def end_element(self):
499 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
500 if not self.is_streaming:
501 if self.macro_name is None:
502 c = OneTimeElement(self.source_id)
504 c = MacroElement(self.source_id, self.macros, self.macro_name)
506 assert self.macro_name is None
507 c = StreamingElement(self.source_id)
509 c.connect(self.source)
511 self.screen.renderer.append(c)
514 def start_convert(self, attrs):
515 ctype = attrs["type"]
517 # TODO: we need something better here
518 if ctype[:4] == "web:": # for now
519 self.converter = eval(ctype[4:])
522 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
524 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
527 def end_convert(self):
528 if len(self.sub) == 1:
529 self.sub = self.sub[0]
530 c = self.converter(self.sub)
531 c.connect(self.source)
535 def parse_item(self, attrs):
537 filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4, "urlencode": 5}[attrs.get("filter", "")]
538 self.sub.append(ListItem(attrs["name"], filter))
540 assert "macro" in attrs, "e2:item must have a name= or macro= attribute!"
541 self.sub.append(ListMacroItem(self.macros, attrs["macro"]))
543 def startElement(self, name, attrs):
544 if name == "e2:screen":
545 self.screen = eval(attrs["name"])(self.session,self.request) # fixme
546 self.screens.append(self.screen)
549 if name[:3] == "e2:":
552 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
556 tag = ''.join(tag)#.encode('utf-8')
560 elif self.mode == 1: # expect "<e2:element>"
561 assert name == "e2:element", "found %s instead of e2:element" % name
562 self.start_element(attrs)
563 elif self.mode == 2: # expect "<e2:convert>"
564 if name[:3] == "e2:":
565 assert name == "e2:convert"
566 self.start_convert(attrs)
570 assert name == "e2:item", "found %s instead of e2:item!" % name
572 self.parse_item(attrs)
574 def endElement(self, name):
575 if name == "e2:screen":
579 tag = "</" + name + ">"
582 elif self.mode == 2 and name[:3] != "e2:":
584 elif self.mode == 2: # closed 'convert' -> sub
586 elif self.mode == 1: # closed 'element'
588 if name[:3] == "e2:":
591 def processingInstruction(self, target, data):
592 self.res.append('<?' + target + ' ' + data + '>')
594 def characters(self, ch):
595 ch = ch.encode('utf-8')
601 def startEntity(self, name):
602 self.res.append('&' + name + ';');
605 for screen in self.screens:
609 print "screen cleanup!"
610 for screen in self.screens:
615 def renderPage(stream, path, req, session):
617 # read in the template, create required screens
618 # we don't have persistense yet.
619 # if we had, this first part would only be done once.
620 handler = webifHandler(session,req)
621 parser = make_parser()
622 parser.setFeature(feature_namespaces, 0)
623 parser.setContentHandler(handler)
624 parser.parse(open(util.sibpath(__file__, path)))
626 # by default, we have non-streaming pages
629 # first, apply "commands" (aka. URL argument)
630 for x in handler.res:
631 if isinstance(x, Element):
632 x.handleCommand(req.args)
636 # now, we have a list with static texts mixed
637 # with non-static Elements.
638 # flatten this list, write into the stream.
639 for x in handler.res:
640 if isinstance(x, Element):
641 if isinstance(x, StreamingElement):
649 from twisted.internet import reactor
651 reactor.callLater(3, ping, s)
653 # if we met a "StreamingElement", there is at least one
654 # element which wants to output data more than once,
655 # i.e. on host-originated changes.
656 # in this case, don't finish yet, don't cleanup yet,
657 # but instead do that when the client disconnects.
659 streamFinish(handler, stream)
662 # you *need* something which constantly sends something in a regular interval,
663 # in order to detect disconnected clients.
664 # i agree that this "ping" sucks terrible, so better be sure to have something
665 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
667 stream.closed_callback = lambda : streamFinish(handler, stream)
669 def streamFinish(handler, stream):