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 Tools.Import import my_import
16 from Screens.InfoBarGenerics import InfoBarServiceName, InfoBarEvent, InfoBarTuner
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.Files import Files
41 from Components.Sources.FrontendStatus import FrontendStatus
43 from Components.Converter.Converter import Converter
45 from Components.Element import Element
47 from xml.sax import make_parser
48 from xml.sax.handler import ContentHandler, feature_namespaces
50 from twisted.python import util
52 # prototype of the new web frontend template system.
54 class WebScreen(Screen):
55 def __init__(self, session, request):
56 Screen.__init__(self, session)
57 self.stand_alone = True
58 self.request = request
61 class DummyWebScreen(WebScreen):
62 #use it, if you dont need any source, just to can do a static file with an xml-file
63 def __init__(self, session,request):
64 WebScreen.__init__(self, session,request)
66 class UpdateWebScreen(InfoBarServiceName, InfoBarEvent,InfoBarTuner,WebScreen):
67 def __init__(self, session,request):
68 WebScreen.__init__(self, session,request)
69 InfoBarServiceName.__init__(self)
70 InfoBarEvent.__init__(self)
71 InfoBarTuner.__init__(self)
72 self["CurrentTime"] = Clock()
73 fav = eServiceReference('1:7:1:0:0:0:0:0:0:0:(type == 1) || (type == 17) || (type == 195) || (type == 25) FROM BOUQUET "bouquets.tv" ORDER BY bouquet')
79 class MessageWebScreen(WebScreen):
80 def __init__(self, session,request):
81 WebScreen.__init__(self, session,request)
82 self["Message"] = Message(session,func = Message.PRINT)
83 self["GetAnswer"] = Message(session,func = Message.ANSWER)
85 class AudioWebScreen(WebScreen):
86 def __init__(self, session,request):
87 WebScreen.__init__(self, session,request)
88 self["AudioTracks"] = AudioTracks(session)
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)
113 fav = eServiceReference('1:7:1:0:0:0:0:0:0:0:(type == 1) || (type == 17) || (type == 195) || (type == 25) FROM BOUQUET "bouquets.tv" ORDER BY bouquet')
114 self["SwitchService"] = ServiceList(fav, command_func = self.zapTo, validate_commands=False)
115 self["ServiceList"] = ServiceList(fav, command_func = self.getServiceList, validate_commands=False)
116 self["ServiceListRecursive"] = ServiceListRecursive(session, func=ServiceListRecursive.FETCH)
118 def getServiceList(self, sRef):
119 self["ServiceList"].root = sRef
121 def zapTo(self, reftozap):
122 from Components.config import config
123 pc = config.ParentalControl.configured.value
125 config.ParentalControl.configured.value = False
126 self.session.nav.playService(reftozap)
128 config.ParentalControl.configured.value = pc
130 switching config.ParentalControl.configured.value
131 ugly, but necessary :(
134 class EPGWebScreen(WebScreen):
135 def __init__(self, session,request):
136 WebScreen.__init__(self, session,request)
137 self["EPGTITLE"] = EPG(session,func=EPG.TITLE)
138 self["EPGSERVICE"] = EPG(session,func=EPG.SERVICE)
139 self["EPGNOW"] = EPG(session,func=EPG.NOW)
141 class MovieWebScreen(WebScreen):
142 def __init__(self, session,request):
143 WebScreen.__init__(self, session,request)
144 from Components.MovieList import MovieList
145 from Tools.Directories import resolveFilename,SCOPE_HDD
146 movielist = MovieList(eServiceReference("2:0:1:0:0:0:0:0:0:0:" + resolveFilename(SCOPE_HDD)))
147 self["MovieList"] = Movie(session,movielist,func = Movie.LIST)
148 self["MovieFileDel"] = Movie(session,movielist,func = Movie.DEL)
149 self["MovieTags"] = Movie(session,movielist,func = Movie.TAGS)
151 class MediaPlayerWebScreen(WebScreen):
152 def __init__(self, session,request):
153 WebScreen.__init__(self, session,request)
154 self["FileList"] = MP(session,func = MP.LIST)
155 self["PlayFile"] = MP(session,func = MP.PLAY)
156 self["Command"] = MP(session,func = MP.COMMAND)
157 self["WritePlaylist"] = MP(session,func = MP.WRITEPLAYLIST)
159 class FilesWebScreen(WebScreen):
160 def __init__(self, session,request):
161 WebScreen.__init__(self, session,request)
162 self["DelFile"] = Files(session,func = Files.DEL)
164 class TimerWebScreen(WebScreen):
165 def __init__(self, session,request):
166 WebScreen.__init__(self, session,request)
167 self["TimerList"] = Timer(session,func = Timer.LIST)
168 self["TimerAddEventID"] = Timer(session,func = Timer.ADDBYID)
169 self["TimerAdd"] = Timer(session,func = Timer.ADD)
170 self["TimerDel"] = Timer(session,func = Timer.DEL)
171 self["TimerChange"] = Timer(session,func = Timer.CHANGE)
172 self["TimerListWrite"] = Timer(session,func = Timer.WRITE)
173 self["TVBrowser"] = Timer(session,func = Timer.TVBROWSER)
174 self["RecordNow"] = Timer(session,func = Timer.RECNOW)
176 class RemoteWebScreen(WebScreen):
177 def __init__(self, session,request):
178 WebScreen.__init__(self, session,request)
179 self["RemoteControl"] = RemoteControl(session)
181 class PowerWebScreen(WebScreen):
182 def __init__(self, session,request):
183 WebScreen.__init__(self, session,request)
184 self["PowerState"] = PowerState(session)
186 class ParentControlWebScreen(WebScreen):
187 def __init__(self, session,request):
188 WebScreen.__init__(self, session,request)
189 self["ParentControlList"] = ParentControl(session)
191 class WAPWebScreen(WebScreen):
192 def __init__(self, session,request):
193 WebScreen.__init__(self, session,request)
194 self["WAPFillOptionListSyear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
195 self["WAPFillOptionListSday"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
196 self["WAPFillOptionListSmonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
197 self["WAPFillOptionListShour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
198 self["WAPFillOptionListSmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
200 self["WAPFillOptionListEyear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
201 self["WAPFillOptionListEday"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
202 self["WAPFillOptionListEmonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
203 self["WAPFillOptionListEhour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
204 self["WAPFillOptionListEmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
206 self["WAPFillOptionListRecord"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
207 self["WAPFillOptionListAfterEvent"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
209 self["WAPFillValueName"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
210 self["WAPFillValueDescr"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
212 self["WAPFillOptionListRepeated"] = WAPfunctions(session,func = WAPfunctions.REPEATED)
213 self["WAPServiceList"] = WAPfunctions(session, func = WAPfunctions.SERVICELIST)
215 self["WAPdeleteOldOnSave"] = WAPfunctions(session,func = WAPfunctions.DELETEOLD)
217 class StreamingWebScreen(WebScreen):
218 def __init__(self, session,request):
219 WebScreen.__init__(self, session,request)
220 from Components.Sources.StreamService import StreamService
221 self["StreamService"] = StreamService(self.session.nav)
223 class M3UStreamingWebScreen(WebScreen):
224 def __init__(self, session,request):
225 WebScreen.__init__(self, session,request)
226 from Components.Sources.StaticText import StaticText
227 from Components.Sources.Config import Config
228 from Components.config import config
229 self["ref"] = StaticText()
230 self["localip"] = RequestData(request,what=RequestData.HOST)
232 class TsM3U(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["file"] = StaticText()
239 self["localip"] = RequestData(request,what=RequestData.HOST)
241 class RestartWebScreen(WebScreen):
242 def __init__(self, session,request):
243 WebScreen.__init__(self, session,request)
245 plugin.restartWebserver()
247 class GetPid(WebScreen):
248 def __init__(self, session,request):
249 WebScreen.__init__(self, session,request)
250 from Components.Sources.StaticText import StaticText
251 from enigma import iServiceInformation
252 pids = self.session.nav.getCurrentService()
254 pidinfo = pids.info()
255 VPID = hex(pidinfo.getInfo(iServiceInformation.sVideoPID))
256 APID = hex(pidinfo.getInfo(iServiceInformation.sAudioPID))
257 PPID = hex(pidinfo.getInfo(iServiceInformation.sPMTPID))
258 self["pids"] = StaticText("%s,%s,%s"%(PPID.lstrip("0x"),VPID.lstrip("0x"),APID.lstrip("0x")))
259 self["localip"] = RequestData(request,what=RequestData.HOST)
262 # implements the 'render'-call.
263 # this will act as a downstream_element, like a renderer.
264 class OneTimeElement(Element):
265 def __init__(self, id):
266 Element.__init__(self)
269 # CHECKME: is this ok performance-wise?
270 def handleCommand(self, args):
271 if self.source_id.find(",") >=0:
272 paramlist = self.source_id.split(",")
274 for key in paramlist:
275 arg = args.get(key, [])
279 list[key] = "".join(arg)
282 self.source.handleCommand(list)
284 for c in args.get(self.source_id, []):
285 self.source.handleCommand(c)
287 def render(self, stream):
288 t = self.source.getHTML(self.source_id)
306 class MacroElement(OneTimeElement):
307 def __init__(self, id, macro_dict, macro_name):
308 OneTimeElement.__init__(self, id)
309 self.macro_dict = macro_dict
310 self.macro_name = macro_name
312 def render(self, stream):
313 self.macro_dict[self.macro_name] = self.source.getHTML(self.source_id)
315 class StreamingElement(OneTimeElement):
316 def __init__(self, id):
317 OneTimeElement.__init__(self, id)
320 def changed(self, what):
322 self.render(self.stream)
324 def setStream(self, stream):
327 # a to-be-filled list item
329 def __init__(self, name, filternum):
331 self.filternum = filternum
334 def __init__(self, macrodict, macroname):
335 self.macrodict = macrodict
336 self.macroname = macroname
338 class TextToHTML(Converter):
339 def __init__(self, arg):
340 Converter.__init__(self, arg)
342 def getHTML(self, id):
343 return self.source.text # encode & etc. here!
345 class TextToURL(Converter):
346 def __init__(self, arg):
347 Converter.__init__(self, arg)
349 def getHTML(self, id):
350 return self.source.text.replace(" ","%20")
352 class ReturnEmptyXML(Converter):
353 def __init__(self, arg):
354 Converter.__init__(self, arg)
356 def getHTML(self, id):
357 return "<rootElement></rootElement>"
359 # a null-output. Useful if you only want to issue a command.
360 class Null(Converter):
361 def __init__(self, arg):
362 Converter.__init__(self, arg)
364 def getHTML(self, id):
367 class JavascriptUpdate(Converter):
368 def __init__(self, arg):
369 Converter.__init__(self, arg)
371 def getHTML(self, id):
372 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
373 # all other will replace this in JS
374 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '°'))
376 # the performant 'listfiller'-engine (plfe)
377 class ListFiller(Converter):
378 def __init__(self, arg):
379 Converter.__init__(self, arg)
380 # print "ListFiller-arg: ",arg
384 lut = self.source.lut
385 conv_args = self.converter_arguments
387 # now build a ["string", 1, "string", 2]-styled list, with indices into the
388 # list to avoid lookup of item name for each entry
390 for element in conv_args:
391 if isinstance(element, basestring):
392 lutlist.append((element, None))
393 elif isinstance(element, ListItem):
394 lutlist.append((lut[element.name], element.filternum))
395 elif isinstance(element, ListMacroItem):
396 lutlist.append((element.macrodict[element.macroname], None))
398 raise "neither string, ListItem nor ListMacroItem"
400 # now, for the huge list, do:
402 append = strlist.append
404 for (element, filternum) in lutlist:
408 append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
410 append(str(item[element]).replace("&", "&").replace("<", "<").replace('"', '"').replace(">", ">"))
412 append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
414 append(str(item[element]))
415 # (this will be done in c++ later!)
416 return ''.join(strlist)
418 text = property(getText)
420 class webifHandler(ContentHandler):
421 def __init__(self, session, request):
425 self.session = session
427 self.request = request
430 def start_element(self, attrs):
433 wsource = attrs["source"]
435 path = wsource.split('.')
437 scr = self.screen.getRelatedScreen(path[0])
439 print "[webif.py] Parent Screen not found!"
443 source = scr.get(path[0])
445 if isinstance(source, ObsoleteSource):
446 # however, if we found an "obsolete source", issue warning, and resolve the real source.
447 print "WARNING: WEBIF '%s' USES OBSOLETE SOURCE '%s', USE '%s' INSTEAD!" % (name, wsource, source.new_source)
448 print "OBSOLETE SOURCE WILL BE REMOVED %s, PLEASE UPDATE!" % (source.removal_date)
449 if source.description:
450 print source.description
452 wsource = source.new_source
455 # otherwise, use that source.
458 self.source_id = str(attrs.get("id", wsource))
459 self.is_streaming = "streaming" in attrs
460 self.macro_name = attrs.get("macro") or None
462 def end_element(self):
463 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
464 if not self.is_streaming:
465 if self.macro_name is None:
466 c = OneTimeElement(self.source_id)
468 c = MacroElement(self.source_id, self.macros, self.macro_name)
470 assert self.macro_name is None
471 c = StreamingElement(self.source_id)
473 c.connect(self.source)
475 self.screen.renderer.append(c)
478 def start_convert(self, attrs):
479 ctype = attrs["type"]
481 # TODO: we need something better here
482 if ctype[:4] == "web:": # for now
483 self.converter = eval(ctype[4:])
486 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
488 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
491 def end_convert(self):
492 if len(self.sub) == 1:
493 self.sub = self.sub[0]
494 c = self.converter(self.sub)
495 c.connect(self.source)
499 def parse_item(self, attrs):
501 filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4}[attrs.get("filter", "")]
502 self.sub.append(ListItem(attrs["name"], filter))
504 assert "macro" in attrs, "e2:item must have a name= or macro= attribute!"
505 self.sub.append(ListMacroItem(self.macros, attrs["macro"]))
507 def startElement(self, name, attrs):
508 if name == "e2:screen":
509 self.screen = eval(attrs["name"])(self.session,self.request) # fixme
510 self.screens.append(self.screen)
513 if name[:3] == "e2:":
516 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
520 tag = ''.join(tag)#.encode('utf-8')
524 elif self.mode == 1: # expect "<e2:element>"
525 assert name == "e2:element", "found %s instead of e2:element" % name
526 self.start_element(attrs)
527 elif self.mode == 2: # expect "<e2:convert>"
528 if name[:3] == "e2:":
529 assert name == "e2:convert"
530 self.start_convert(attrs)
534 assert name == "e2:item", "found %s instead of e2:item!" % name
535 self.parse_item(attrs)
537 def endElement(self, name):
538 if name == "e2:screen":
542 tag = "</" + name + ">"
545 elif self.mode == 2 and name[:3] != "e2:":
547 elif self.mode == 2: # closed 'convert' -> sub
549 elif self.mode == 1: # closed 'element'
551 if name[:3] == "e2:":
554 def processingInstruction(self, target, data):
555 self.res.append('<?' + target + ' ' + data + '>')
557 def characters(self, ch):
558 ch = ch.encode('utf-8')
564 def startEntity(self, name):
565 self.res.append('&' + name + ';');
568 for screen in self.screens:
572 print "screen cleanup!"
573 for screen in self.screens:
578 def renderPage(stream, path, req, session):
580 # read in the template, create required screens
581 # we don't have persistense yet.
582 # if we had, this first part would only be done once.
583 handler = webifHandler(session,req)
584 parser = make_parser()
585 parser.setFeature(feature_namespaces, 0)
586 parser.setContentHandler(handler)
587 parser.parse(open(util.sibpath(__file__, path)))
589 # by default, we have non-streaming pages
592 # first, apply "commands" (aka. URL argument)
593 for x in handler.res:
594 if isinstance(x, Element):
595 x.handleCommand(req.args)
599 # now, we have a list with static texts mixed
600 # with non-static Elements.
601 # flatten this list, write into the stream.
602 for x in handler.res:
603 if isinstance(x, Element):
604 if isinstance(x, StreamingElement):
612 from twisted.internet import reactor
614 reactor.callLater(3, ping, s)
616 # if we met a "StreamingElement", there is at least one
617 # element which wants to output data more than once,
618 # i.e. on host-originated changes.
619 # in this case, don't finish yet, don't cleanup yet,
620 # but instead do that when the client disconnects.
626 # you *need* something which constantly sends something in a regular interval,
627 # in order to detect disconnected clients.
628 # i agree that this "ping" sucks terrible, so better be sure to have something
629 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
631 stream.closed_callback = lambda: handler.cleanup()