3 # OK, this is more than a proof of concept
6 # - screens need to be defined somehow else.
7 # I don't know how, yet. Probably each in an own file.
8 # - more components, like the channellist
9 # - better error handling
10 # - use namespace parser
11 from enigma import eServiceReference
13 from Screens.Screen import Screen
14 from Screens.ChannelSelection import service_types_tv, service_types_radio
15 from Tools.Import import my_import
17 from Components.Sources.Source import ObsoleteSource
19 from Components.Sources.Clock import Clock
20 from Components.Sources.ServiceList import ServiceList
22 from WebComponents.Sources.ServiceListRecursive import ServiceListRecursive
23 from WebComponents.Sources.Volume import Volume
24 from WebComponents.Sources.EPG import EPG
25 from WebComponents.Sources.Timer import Timer
26 from WebComponents.Sources.Movie import Movie
27 from WebComponents.Sources.Message import Message
28 from WebComponents.Sources.PowerState import PowerState
29 from WebComponents.Sources.RemoteControl import RemoteControl
30 from WebComponents.Sources.Settings import Settings
31 from WebComponents.Sources.SubServices import SubServices
32 from WebComponents.Sources.ParentControl import ParentControl
33 from WebComponents.Sources.About import About
34 from WebComponents.Sources.RequestData import RequestData
35 from WebComponents.Sources.AudioTracks import AudioTracks
36 from WebComponents.Sources.WAPfunctions import WAPfunctions
37 from WebComponents.Sources.MP import MP
38 from WebComponents.Sources.Files import Files
39 from WebComponents.Sources.ServiceListReload import ServiceListReload
40 from WebComponents.Sources.AT import AT
42 from Components.Sources.FrontendStatus import FrontendStatus
44 from Components.Converter.Converter import Converter
46 from Components.Element import Element
48 from xml.sax import make_parser
49 from xml.sax.handler import ContentHandler, feature_namespaces
50 from xml.sax.saxutils import escape as escape_xml
51 from twisted.python import util
53 # prototype of the new web frontend template system.
55 class WebScreen(Screen):
56 def __init__(self, session, request):
57 Screen.__init__(self, session)
58 self.stand_alone = True
59 self.request = request
62 class DummyWebScreen(WebScreen):
63 #use it, if you dont need any source, just to can do a static file with an xml-file
64 def __init__(self, session, request):
65 WebScreen.__init__(self, session, request)
67 class UpdateWebScreen(WebScreen):
68 def __init__(self, session, request):
69 WebScreen.__init__(self, session, request)
70 self["CurrentTime"] = Clock()
71 fav = eServiceReference(service_types_tv + ' FROM BOUQUET "bouquets.tv" ORDER BY bouquet')
73 class MessageWebScreen(WebScreen):
74 def __init__(self, session, request):
75 WebScreen.__init__(self, session, request)
76 self["Message"] = Message(session,func = Message.PRINT)
77 self["GetAnswer"] = Message(session,func = Message.ANSWER)
79 class ServiceListReloadWebScreen(WebScreen):
80 def __init__(self, session, request):
81 WebScreen.__init__(self, session, request)
82 self["ServiceListReload"] = ServiceListReload(session)
84 class AudioWebScreen(WebScreen):
85 def __init__(self, session, request):
86 WebScreen.__init__(self, session, request)
87 self["AudioTracks"] = AudioTracks(session, func=AudioTracks.GET)
88 self["SelectAudioTrack"] = AudioTracks(session, func=AudioTracks.SET)
90 class AboutWebScreen(WebScreen):
91 def __init__(self, session, request):
92 WebScreen.__init__(self, session, request)
93 self["About"] = About(session)
95 class VolumeWebScreen(WebScreen):
96 def __init__(self, session, request):
97 WebScreen.__init__(self, session, request)
98 self["Volume"] = Volume(session)
100 class SettingsWebScreen(WebScreen):
101 def __init__(self, session, request):
102 WebScreen.__init__(self, session, request)
103 self["Settings"] = Settings(session)
105 class SubServiceWebScreen(WebScreen):
106 def __init__(self, session, request):
107 WebScreen.__init__(self, session, request)
108 self["SubServices"] = SubServices(session)
110 class ServiceWebScreen(WebScreen):
111 def __init__(self, session, request):
112 WebScreen.__init__(self, session, request)
114 fav = eServiceReference(service_types_tv + ' FROM BOUQUET "bouquets.tv" ORDER BY bouquet')
115 self["SwitchService"] = ServiceList(fav, command_func = self.zapTo, validate_commands=False)
116 self["ServiceList"] = ServiceList(fav, command_func = self.getServiceList, validate_commands=False)
117 self["ServiceListRecursive"] = ServiceListRecursive(session, func=ServiceListRecursive.FETCH)
119 def getServiceList(self, sRef):
120 self["ServiceList"].root = sRef
122 def zapTo(self, reftozap):
123 from Components.config import config
124 pc = config.ParentalControl.configured.value
126 config.ParentalControl.configured.value = False
127 if config.plugins.Webinterface.allowzapping.value:
128 self.session.nav.playService(reftozap)
130 config.ParentalControl.configured.value = pc
132 switching config.ParentalControl.configured.value
133 ugly, but necessary :(
136 class EPGWebScreen(WebScreen):
137 def __init__(self, session, request):
138 WebScreen.__init__(self, session, request)
139 self["EPGTITLE"] = EPG(session,func=EPG.TITLE)
140 self["EPGSERVICE"] = EPG(session,func=EPG.SERVICE)
141 self["EPGNOW"] = EPG(session,func=EPG.NOW)
142 self["EPGNEXT"] = EPG(session,func=EPG.NEXT)
144 class MovieWebScreen(WebScreen):
145 def __init__(self, session, request):
146 WebScreen.__init__(self, session, request)
147 from Components.MovieList import MovieList
148 from Tools.Directories import resolveFilename,SCOPE_HDD
149 movielist = MovieList(eServiceReference("2:0:1:0:0:0:0:0:0:0:" + resolveFilename(SCOPE_HDD)))
150 self["MovieList"] = Movie(session,movielist,func = Movie.LIST)
151 self["MovieFileDel"] = Movie(session,movielist,func = Movie.DEL)
152 self["MovieTags"] = Movie(session,movielist,func = Movie.TAGS)
154 class MediaPlayerWebScreen(WebScreen):
155 def __init__(self, session, request):
156 WebScreen.__init__(self, session, request)
157 self["FileList"] = MP(session,func = MP.LIST)
158 self["PlayFile"] = MP(session,func = MP.PLAY)
159 self["Command"] = MP(session,func = MP.COMMAND)
160 self["WritePlaylist"] = MP(session,func = MP.WRITEPLAYLIST)
162 class AutoTimerWebScreen(WebScreen):
163 def __init__(self, session, request):
164 WebScreen.__init__(self, session, request)
165 self["AutoTimerList"] = AT(session,func = AT.LIST)
166 self["AutoTimerWrite"] = AT(session,func = AT.WRITE)
168 class FilesWebScreen(WebScreen):
169 def __init__(self, session, request):
170 WebScreen.__init__(self, session, request)
171 self["DelFile"] = Files(session,func = Files.DEL)
173 class TimerWebScreen(WebScreen):
174 def __init__(self, session, request):
175 WebScreen.__init__(self, session, request)
176 self["TimerList"] = Timer(session,func = Timer.LIST)
177 self["TimerAddEventID"] = Timer(session,func = Timer.ADDBYID)
178 self["TimerAdd"] = Timer(session,func = Timer.ADD)
179 self["TimerDel"] = Timer(session,func = Timer.DEL)
180 self["TimerChange"] = Timer(session,func = Timer.CHANGE)
181 self["TimerListWrite"] = Timer(session,func = Timer.WRITE)
182 self["TVBrowser"] = Timer(session,func = Timer.TVBROWSER)
183 self["RecordNow"] = Timer(session,func = Timer.RECNOW)
185 class RemoteWebScreen(WebScreen):
186 def __init__(self, session, request):
187 WebScreen.__init__(self, session, request)
188 self["RemoteControl"] = RemoteControl(session)
190 class PowerWebScreen(WebScreen):
191 def __init__(self, session, request):
192 WebScreen.__init__(self, session, request)
193 self["PowerState"] = PowerState(session)
195 class ParentControlWebScreen(WebScreen):
196 def __init__(self, session, request):
197 WebScreen.__init__(self, session, request)
198 self["ParentControlList"] = ParentControl(session)
200 class WAPWebScreen(WebScreen):
201 def __init__(self, session, request):
202 WebScreen.__init__(self, session, request)
203 self["WAPFillOptionListSyear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
204 self["WAPFillOptionListSday"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
205 self["WAPFillOptionListSmonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
206 self["WAPFillOptionListShour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
207 self["WAPFillOptionListSmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
209 self["WAPFillOptionListEyear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
210 self["WAPFillOptionListEday"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
211 self["WAPFillOptionListEmonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
212 self["WAPFillOptionListEhour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
213 self["WAPFillOptionListEmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
215 self["WAPFillOptionListRecord"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
216 self["WAPFillOptionListAfterEvent"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
218 self["WAPFillValueName"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
219 self["WAPFillValueDescr"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
221 self["WAPFillOptionListRepeated"] = WAPfunctions(session,func = WAPfunctions.REPEATED)
222 self["WAPServiceList"] = WAPfunctions(session, func = WAPfunctions.SERVICELIST)
224 self["WAPdeleteOldOnSave"] = WAPfunctions(session,func = WAPfunctions.DELETEOLD)
226 class StreamingWebScreen(WebScreen):
227 def __init__(self, session, request):
228 WebScreen.__init__(self, session, request)
229 from Components.Sources.StreamService import StreamService
230 self["StreamService"] = StreamService(self.session.nav)
232 class M3UStreamingWebScreen(WebScreen):
233 def __init__(self, session, request):
234 WebScreen.__init__(self, session, request)
235 from Components.Sources.StaticText import StaticText
236 from Components.Sources.Config import Config
237 from Components.config import config
238 self["ref"] = StaticText()
239 self["localip"] = RequestData(request,what=RequestData.HOST)
241 class TsM3U(WebScreen):
242 def __init__(self, session, request):
243 WebScreen.__init__(self, session, request)
244 from Components.Sources.StaticText import StaticText
245 from Components.Sources.Config import Config
246 from Components.config import config
247 self["file"] = StaticText()
248 self["localip"] = RequestData(request,what=RequestData.HOST)
250 class RestartWebScreen(WebScreen):
251 def __init__(self, session, request):
252 WebScreen.__init__(self, session, request)
254 plugin.restartWebserver(session)
256 class GetPid(WebScreen):
257 def __init__(self, session, request):
258 WebScreen.__init__(self, session, request)
259 from Components.Sources.StaticText import StaticText
260 from enigma import iServiceInformation
261 pids = self.session.nav.getCurrentService()
263 pidinfo = pids.info()
264 VPID = hex(pidinfo.getInfo(iServiceInformation.sVideoPID))
265 APID = hex(pidinfo.getInfo(iServiceInformation.sAudioPID))
266 PPID = hex(pidinfo.getInfo(iServiceInformation.sPMTPID))
267 self["pids"] = StaticText("%s,%s,%s"%(PPID.lstrip("0x"),VPID.lstrip("0x"),APID.lstrip("0x")))
269 self["pids"] = StaticText("0x,0x,0x")
271 self["localip"] = RequestData(request,what=RequestData.HOST)
274 # implements the 'render'-call.
275 # this will act as a downstream_element, like a renderer.
276 class OneTimeElement(Element):
277 def __init__(self, id):
278 Element.__init__(self)
281 # CHECKME: is this ok performance-wise?
282 def handleCommand(self, args):
283 if self.source_id.find(",") >=0:
284 paramlist = self.source_id.split(",")
286 for key in paramlist:
287 arg = args.get(key, [])
291 list[key] = "".join(arg)
294 self.source.handleCommand(list)
296 for c in args.get(self.source_id, []):
297 self.source.handleCommand(c)
299 def render(self, stream):
300 t = self.source.getHTML(self.source_id)
304 self.suspended = False
307 self.suspended = True
318 class MacroElement(OneTimeElement):
319 def __init__(self, id, macro_dict, macro_name):
320 OneTimeElement.__init__(self, id)
321 self.macro_dict = macro_dict
322 self.macro_name = macro_name
324 def render(self, stream):
325 self.macro_dict[self.macro_name] = self.source.getHTML(self.source_id)
327 class StreamingElement(OneTimeElement):
328 def __init__(self, id):
329 OneTimeElement.__init__(self, id)
332 def changed(self, what):
334 self.render(self.stream)
336 def setStream(self, stream):
339 # a to-be-filled list item
341 def __init__(self, name, filternum):
343 self.filternum = filternum
346 def __init__(self, macrodict, macroname):
347 self.macrodict = macrodict
348 self.macroname = macroname
350 class TextToHTML(Converter):
351 def __init__(self, arg):
352 Converter.__init__(self, arg)
354 def getHTML(self, id):
355 return self.source.text # encode & etc. here!
357 class TextToXML(Converter):
358 def __init__(self, arg):
359 Converter.__init__(self, arg)
361 def getHTML(self, id):
362 return escape_xml(self.source.text).replace("\x19", "").replace("\x1c", "").replace("\x1e", "")
364 class TextToURL(Converter):
365 def __init__(self, arg):
366 Converter.__init__(self, arg)
368 def getHTML(self, id):
369 return self.source.text.replace(" ","%20")
371 class ReturnEmptyXML(Converter):
372 def __init__(self, arg):
373 Converter.__init__(self, arg)
375 def getHTML(self, id):
376 return "<rootElement></rootElement>"
378 # a null-output. Useful if you only want to issue a command.
379 class Null(Converter):
380 def __init__(self, arg):
381 Converter.__init__(self, arg)
383 def getHTML(self, id):
386 class JavascriptUpdate(Converter):
387 def __init__(self, arg):
388 Converter.__init__(self, arg)
390 def getHTML(self, id):
391 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
392 # all other will replace this in JS
393 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '°'))
395 # the performant 'listfiller'-engine (plfe)
396 class ListFiller(Converter):
397 def __init__(self, arg):
398 Converter.__init__(self, arg)
399 # print "ListFiller-arg: ",arg
403 lut = self.source.lut
404 conv_args = self.converter_arguments
406 # now build a ["string", 1, "string", 2]-styled list, with indices into the
407 # list to avoid lookup of item name for each entry
409 for element in conv_args:
410 if isinstance(element, basestring):
411 lutlist.append((element, None))
412 elif isinstance(element, ListItem):
413 lutlist.append((lut[element.name], element.filternum))
414 elif isinstance(element, ListMacroItem):
415 lutlist.append((element.macrodict[element.macroname], None))
417 raise "neither string, ListItem nor ListMacroItem"
419 # now, for the huge list, do:
421 append = strlist.append
423 for (element, filternum) in lutlist:
427 append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
429 append(escape_xml(str(item[element])))
431 append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
433 append(str(item[element]))
434 # (this will be done in c++ later!)
436 return ''.join(strlist)
438 text = property(getText)
440 class webifHandler(ContentHandler):
441 def __init__(self, session, request):
445 self.session = session
447 self.request = request
450 def start_element(self, attrs):
453 wsource = attrs["source"]
455 path = wsource.split('.')
457 scr = self.screen.getRelatedScreen(path[0])
459 print "[webif.py] Parent Screen not found!"
463 source = scr.get(path[0])
465 if isinstance(source, ObsoleteSource):
466 # however, if we found an "obsolete source", issue warning, and resolve the real source.
467 print "WARNING: WEBIF '%s' USES OBSOLETE SOURCE '%s', USE '%s' INSTEAD!" % (name, wsource, source.new_source)
468 print "OBSOLETE SOURCE WILL BE REMOVED %s, PLEASE UPDATE!" % (source.removal_date)
469 if source.description:
470 print source.description
472 wsource = source.new_source
475 # otherwise, use that source.
478 self.source_id = str(attrs.get("id", wsource))
479 self.is_streaming = "streaming" in attrs
480 self.macro_name = attrs.get("macro") or None
482 def end_element(self):
483 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
484 if not self.is_streaming:
485 if self.macro_name is None:
486 c = OneTimeElement(self.source_id)
488 c = MacroElement(self.source_id, self.macros, self.macro_name)
490 assert self.macro_name is None
491 c = StreamingElement(self.source_id)
493 c.connect(self.source)
495 self.screen.renderer.append(c)
498 def start_convert(self, attrs):
499 ctype = attrs["type"]
501 # TODO: we need something better here
502 if ctype[:4] == "web:": # for now
503 self.converter = eval(ctype[4:])
506 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
508 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
511 def end_convert(self):
512 if len(self.sub) == 1:
513 self.sub = self.sub[0]
514 c = self.converter(self.sub)
515 c.connect(self.source)
519 def parse_item(self, attrs):
521 filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4}[attrs.get("filter", "")]
522 self.sub.append(ListItem(attrs["name"], filter))
524 assert "macro" in attrs, "e2:item must have a name= or macro= attribute!"
525 self.sub.append(ListMacroItem(self.macros, attrs["macro"]))
527 def startElement(self, name, attrs):
528 if name == "e2:screen":
529 self.screen = eval(attrs["name"])(self.session,self.request) # fixme
530 self.screens.append(self.screen)
533 if name[:3] == "e2:":
536 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
540 tag = ''.join(tag)#.encode('utf-8')
544 elif self.mode == 1: # expect "<e2:element>"
545 assert name == "e2:element", "found %s instead of e2:element" % name
546 self.start_element(attrs)
547 elif self.mode == 2: # expect "<e2:convert>"
548 if name[:3] == "e2:":
549 assert name == "e2:convert"
550 self.start_convert(attrs)
554 assert name == "e2:item", "found %s instead of e2:item!" % name
556 self.parse_item(attrs)
558 def endElement(self, name):
559 if name == "e2:screen":
563 tag = "</" + name + ">"
566 elif self.mode == 2 and name[:3] != "e2:":
568 elif self.mode == 2: # closed 'convert' -> sub
570 elif self.mode == 1: # closed 'element'
572 if name[:3] == "e2:":
575 def processingInstruction(self, target, data):
576 self.res.append('<?' + target + ' ' + data + '>')
578 def characters(self, ch):
579 ch = ch.encode('utf-8')
585 def startEntity(self, name):
586 self.res.append('&' + name + ';');
589 for screen in self.screens:
593 print "screen cleanup!"
594 for screen in self.screens:
599 def renderPage(stream, path, req, session):
601 # read in the template, create required screens
602 # we don't have persistense yet.
603 # if we had, this first part would only be done once.
604 handler = webifHandler(session,req)
605 parser = make_parser()
606 parser.setFeature(feature_namespaces, 0)
607 parser.setContentHandler(handler)
608 parser.parse(open(util.sibpath(__file__, path)))
610 # by default, we have non-streaming pages
613 # first, apply "commands" (aka. URL argument)
614 for x in handler.res:
615 if isinstance(x, Element):
616 x.handleCommand(req.args)
620 # now, we have a list with static texts mixed
621 # with non-static Elements.
622 # flatten this list, write into the stream.
623 for x in handler.res:
624 if isinstance(x, Element):
625 if isinstance(x, StreamingElement):
633 from twisted.internet import reactor
635 reactor.callLater(3, ping, s)
637 # if we met a "StreamingElement", there is at least one
638 # element which wants to output data more than once,
639 # i.e. on host-originated changes.
640 # in this case, don't finish yet, don't cleanup yet,
641 # but instead do that when the client disconnects.
643 streamFinish(handler, stream)
646 # you *need* something which constantly sends something in a regular interval,
647 # in order to detect disconnected clients.
648 # i agree that this "ping" sucks terrible, so better be sure to have something
649 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
651 stream.closed_callback = lambda : streamFinish(handler, stream)
653 def streamFinish(handler, stream):