* Improve About
[enigma2-plugins.git] / webinterface / src / webif.py
1 Version = '$Header$';
2
3 # OK, this is more than a proof of concept
4 # things to improve:
5 #  - nicer code
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
12
13 from Screens.Screen import Screen
14 from Tools.Import import my_import
15
16 from Screens.InfoBarGenerics import InfoBarServiceName, InfoBarEvent, InfoBarTuner
17
18 from Components.Sources.Clock import Clock
19 from Components.Sources.ServiceList import ServiceList
20
21 from WebComponents.Sources.ServiceListRecursive import ServiceListRecursive
22 from WebComponents.Sources.Volume import Volume
23 from WebComponents.Sources.EPG import EPG
24 from WebComponents.Sources.Timer import Timer
25 from WebComponents.Sources.Movie import Movie
26 from WebComponents.Sources.Message import Message
27 from WebComponents.Sources.PowerState import PowerState
28 from WebComponents.Sources.RemoteControl import RemoteControl
29 from WebComponents.Sources.Settings import Settings
30 from WebComponents.Sources.SubServices import SubServices
31 from WebComponents.Sources.ParentControl import ParentControl
32 from WebComponents.Sources.About import About
33 from WebComponents.Sources.RequestData import RequestData
34 from WebComponents.Sources.AudioTracks import AudioTracks
35 from WebComponents.Sources.WAPfunctions import WAPfunctions
36 from WebComponents.Sources.MP import MP
37
38 from Components.Sources.FrontendStatus import FrontendStatus
39
40 from Components.Converter.Converter import Converter
41
42 from Components.Element import Element
43
44 from xml.sax import make_parser
45 from xml.sax.handler import ContentHandler, feature_namespaces
46
47 from twisted.python import util
48
49 import sys
50 import time
51  
52 # prototype of the new web frontend template system.
53
54 class WebScreen(Screen):
55         def __init__(self, session, request):
56                 Screen.__init__(self, session)
57                 self.stand_alone = True
58                 self.request = request
59                 self.instance = None
60                 
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)
65
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')
74                 #CurrentService
75                 #Event_Now
76                 #Event_Next
77                 #FrontendStatus
78                 
79 class MessageWebScreen(WebScreen):
80         def __init__(self, session,request):
81                 WebScreen.__init__(self, session,request)
82                 self["Message"] = Message(session)
83
84 class AudioWebScreen(WebScreen):
85         def __init__(self, session,request):
86                 WebScreen.__init__(self, session,request)
87                 self["AudioTracks"] = AudioTracks(session)              
88
89 class AboutWebScreen(WebScreen):
90         def __init__(self, session,request):
91                 WebScreen.__init__(self, session,request)
92                 self["About"] = About(session)
93                 
94 class VolumeWebScreen(WebScreen):
95         def __init__(self, session,request):
96                 WebScreen.__init__(self, session,request)
97                 self["Volume"] = Volume(session)
98
99 class SettingsWebScreen(WebScreen):
100         def __init__(self, session,request):
101                 WebScreen.__init__(self, session,request)
102                 self["Settings"] = Settings(session)
103
104 class SubServiceWebScreen(WebScreen):
105         def __init__(self, session,request):
106                 WebScreen.__init__(self, session,request)
107                 self["SubServices"] = SubServices(session)
108
109 class ServiceWebScreen(WebScreen):
110         def __init__(self, session,request):
111                 WebScreen.__init__(self, session,request)
112                 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')
113                 self["SwitchService"] = ServiceList(fav, command_func = self.zapTo, validate_commands=False)
114                 self["ServiceList"] = ServiceList(fav, command_func = self.getServiceList, validate_commands=False)
115                 self["ServiceListRecursive"] = ServiceListRecursive(session, func=ServiceListRecursive.FETCH)
116
117         def getServiceList(self, sRef):
118                 self["ServiceList"].root = sRef
119
120         def zapTo(self, reftozap):
121                 from Components.config import config
122                 pc = config.ParentalControl.configured.value
123                 if pc:
124                         config.ParentalControl.configured.value = False
125                 self.session.nav.playService(reftozap)
126                 if pc:
127                         config.ParentalControl.configured.value = pc
128                 """
129                 switching config.ParentalControl.configured.value
130                 ugly, but necessary :(
131                 """
132
133 class EPGWebScreen(WebScreen):
134         def __init__(self, session,request):
135                 WebScreen.__init__(self, session,request)
136                 self["EPGTITLE"] = EPG(session,func=EPG.TITLE)
137                 self["EPGSERVICE"] = EPG(session,func=EPG.SERVICE)
138                 self["EPGNOW"] = EPG(session,func=EPG.NOW)
139
140 class MovieWebScreen(WebScreen):
141         def __init__(self, session,request):
142                 WebScreen.__init__(self, session,request)
143                 from Components.MovieList import MovieList
144                 from Tools.Directories import resolveFilename,SCOPE_HDD
145                 movielist = MovieList(eServiceReference("2:0:1:0:0:0:0:0:0:0:" + resolveFilename(SCOPE_HDD)))
146                 self["MovieList"] = Movie(session,movielist,func = Movie.LIST)
147                 self["MovieFileDel"] = Movie(session,movielist,func = Movie.DEL)
148                 self["MovieTags"] = Movie(session,movielist,func = Movie.TAGS)
149
150 class MediaPlayerWebScreen(WebScreen):
151         def __init__(self, session,request):
152                 WebScreen.__init__(self, session,request)
153                 self["FileList"] = MP(session,func = MP.LIST)
154                 self["PlayFile"] = MP(session,func = MP.PLAY)
155                 
156                 
157 class TimerWebScreen(WebScreen):
158         def __init__(self, session,request):
159                 WebScreen.__init__(self, session,request)
160                 self["TimerList"] = Timer(session,func = Timer.LIST)
161                 self["TimerAddEventID"] = Timer(session,func = Timer.ADDBYID)
162                 self["TimerAdd"] = Timer(session,func = Timer.ADD)
163                 self["TimerDel"] = Timer(session,func = Timer.DEL)
164                 self["TimerChange"] = Timer(session,func = Timer.CHANGE)
165                 self["TimerListWrite"] = Timer(session,func = Timer.WRITE)
166                 self["TVBrowser"] = Timer(session,func = Timer.TVBROWSER)
167                 self["RecordNow"] = Timer(session,func = Timer.RECNOW)
168
169 class RemoteWebScreen(WebScreen):
170         def __init__(self, session,request):
171                 WebScreen.__init__(self, session,request)
172                 self["RemoteControl"] = RemoteControl(session)
173
174 class PowerWebScreen(WebScreen):
175         def __init__(self, session,request):
176                 WebScreen.__init__(self, session,request)
177                 self["PowerState"] = PowerState(session)
178
179 class ParentControlWebScreen(WebScreen):
180         def __init__(self, session,request):
181                 WebScreen.__init__(self, session,request)
182                 self["ParentControlList"] = ParentControl(session)
183                                 
184 class WAPWebScreen(WebScreen):
185         def __init__(self, session,request):
186                 WebScreen.__init__(self, session,request)
187                 self["WAPFillOptionListSyear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
188                 self["WAPFillOptionListSday"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
189                 self["WAPFillOptionListSmonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
190                 self["WAPFillOptionListShour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
191                 self["WAPFillOptionListSmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
192                 
193                 self["WAPFillOptionListEyear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
194                 self["WAPFillOptionListEday"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
195                 self["WAPFillOptionListEmonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
196                 self["WAPFillOptionListEhour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
197                 self["WAPFillOptionListEmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
198                 
199                 self["WAPFillOptionListRecord"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
200                 self["WAPFillOptionListAfterEvent"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
201                 
202                 self["WAPFillValueName"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
203                 self["WAPFillValueDescr"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
204
205                 self["WAPFillOptionListRepeated"] = WAPfunctions(session,func = WAPfunctions.REPEATED)
206                 self["WAPServiceList"] = WAPfunctions(session, func = WAPfunctions.SERVICELIST)
207
208                 self["WAPdeleteOldOnSave"] = WAPfunctions(session,func = WAPfunctions.DELETEOLD)
209         
210 class StreamingWebScreen(WebScreen):
211         def __init__(self, session,request):
212                 WebScreen.__init__(self, session,request)
213                 from Components.Sources.StreamService import StreamService
214                 self["StreamService"] = StreamService(self.session.nav)
215
216 class M3UStreamingWebScreen(WebScreen):
217         def __init__(self, session,request):
218                 WebScreen.__init__(self, session,request)
219                 from Components.Sources.StaticText import StaticText
220                 from Components.Sources.Config import Config
221                 from Components.config import config
222                 self["ref"] = StaticText()
223                 self["localip"] = RequestData(request,what=RequestData.HOST)
224
225 class TsM3U(WebScreen):
226         def __init__(self, session,request):
227                 WebScreen.__init__(self, session,request)
228                 from Components.Sources.StaticText import StaticText
229                 from Components.Sources.Config import Config
230                 from Components.config import config
231                 self["file"] = StaticText()
232                 self["localip"] = RequestData(request,what=RequestData.HOST)
233
234 class RestartWebScreen(WebScreen):
235         def __init__(self, session,request):
236                 WebScreen.__init__(self, session,request)
237                 import plugin
238                 plugin.restartWebserver()
239                 
240 class GetPid(WebScreen):
241       def __init__(self, session,request):
242          WebScreen.__init__(self, session,request)
243          from Components.Sources.StaticText import StaticText
244          from enigma import iServiceInformation
245          pids = self.session.nav.getCurrentService()
246          if pids is not None:
247                  pidinfo = pids.info()
248                  VPID = hex(pidinfo.getInfo(iServiceInformation.sVideoPID))
249                  APID = hex(pidinfo.getInfo(iServiceInformation.sAudioPID))
250                  PPID = hex(pidinfo.getInfo(iServiceInformation.sPMTPID))
251          self["pids"] = StaticText("%s,%s,%s"%(PPID.lstrip("0x"),VPID.lstrip("0x"),APID.lstrip("0x")))
252          self["localip"] = RequestData(request,what=RequestData.HOST)
253
254
255 # implements the 'render'-call.
256 # this will act as a downstream_element, like a renderer.
257 class OneTimeElement(Element):
258         def __init__(self, id):
259                 Element.__init__(self)
260                 self.source_id = id
261
262         # CHECKME: is this ok performance-wise?
263         def handleCommand(self, args):
264                 if self.source_id.find(",") >=0:
265                         paramlist = self.source_id.split(",")
266                         list={}
267                         for key in paramlist:
268                                 arg = args.get(key, [])
269                                 if len(arg) == 0:
270                                         list[key] = None        
271                                 elif len(arg) == 1:
272                                         list[key] = "".join(arg)        
273                                 elif len(arg) == 2:
274                                         list[key] = arg[0]
275                         self.source.handleCommand(list)
276                 else:
277                         for c in args.get(self.source_id, []):
278                                 self.source.handleCommand(c)
279                 
280         def render(self, stream):
281                 t = self.source.getHTML(self.source_id)
282                 stream.write(t)
283
284         def execBegin(self):
285                 pass
286         
287         def execEnd(self):
288                 pass
289         
290         def onShow(self):
291                 pass
292
293         def onHide(self):
294                 pass
295         
296         def destroy(self):
297                 pass
298
299 class StreamingElement(OneTimeElement):
300         def __init__(self, id):
301                 OneTimeElement.__init__(self, id)
302                 self.stream = None
303
304         def changed(self, what):
305                 if self.stream:
306                         self.render(self.stream)
307
308         def setStream(self, stream):
309                 self.stream = stream
310
311 # a to-be-filled list item
312 class ListItem:
313         def __init__(self, name, filternum):
314                 self.name = name
315                 self.filternum = filternum
316         
317 class TextToHTML(Converter):
318         def __init__(self, arg):
319                 Converter.__init__(self, arg)
320
321         def getHTML(self, id):
322                 return self.source.text # encode & etc. here!
323
324 class TextToURL(Converter):
325         def __init__(self, arg):
326                 Converter.__init__(self, arg)
327
328         def getHTML(self, id):
329                 return self.source.text.replace(" ","%20")
330
331 class ReturnEmptyXML(Converter):
332         def __init__(self, arg):
333                 Converter.__init__(self, arg)
334                 
335         def getHTML(self, id):
336                 return "<rootElement></rootElement>"
337
338 # a null-output. Useful if you only want to issue a command.
339 class Null(Converter):
340         def __init__(self, arg):
341                 Converter.__init__(self, arg)
342
343         def getHTML(self, id):
344                 return ""
345
346 class JavascriptUpdate(Converter):
347         def __init__(self, arg):
348                 Converter.__init__(self, arg)
349
350         def getHTML(self, id):
351                 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
352                 #                all other will replace this in JS
353                 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '&deg;'))
354
355 # the performant 'listfiller'-engine (plfe)
356 class ListFiller(Converter):
357         def __init__(self, arg):
358                 Converter.__init__(self, arg)
359 #               print "ListFiller-arg: ",arg
360
361         def getText(self):
362                 l = self.source.list
363                 lut = self.source.lut
364                 conv_args = self.converter_arguments
365
366                 # now build a ["string", 1, "string", 2]-styled list, with indices into the
367                 # list to avoid lookup of item name for each entry
368                 lutlist = [ isinstance(element, basestring) and (element, None) or (lut[element.name], element.filternum) for element in conv_args ]
369
370                 # now, for the huge list, do:
371                 strlist = [ ]
372                 append = strlist.append
373                 for item in l:
374                         for (element, filternum) in lutlist:
375                                 if not filternum:
376                                         append(element)
377                                 elif filternum == 2:
378                                         append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
379                                 elif filternum == 3:
380                                         append(str(item[element]).replace("&", "&amp;").replace("<", "&lt;").replace('"', '&quot;').replace(">", "&gt;"))
381                                 elif filternum == 4:
382                                         append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
383                                 else:
384                                         append(str(item[element]))
385                 # (this will be done in c++ later!)
386                 return ''.join(strlist)
387
388         text = property(getText)
389
390 class webifHandler(ContentHandler):
391         def __init__(self, session,request):
392                 self.res = [ ]
393                 self.mode = 0
394                 self.screen = None
395                 self.session = session
396                 self.screens = [ ]
397                 self.request = request
398         
399         def startElement(self, name, attrs):
400                 if name == "e2:screen":
401                         self.screen = eval(attrs["name"])(self.session,self.request) # fixme
402                         self.screens.append(self.screen)
403                         return
404         
405                 if name[:3] == "e2:":
406                         self.mode += 1
407
408                 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
409                 tag.insert(0, name)
410                 tag.insert(0, '<')
411                 tag.append('>')
412                 tag = ''.join(tag)#.encode('utf-8')
413
414                 if self.mode == 0:
415                         self.res.append(tag)
416                 elif self.mode == 1: # expect "<e2:element>"
417                         assert name == "e2:element", "found %s instead of e2:element" % name
418                         source = attrs["source"]
419                         self.source_id = str(attrs.get("id", source))
420                         self.source = self.screen[source]
421                         self.is_streaming = "streaming" in attrs
422                 elif self.mode == 2: # expect "<e2:convert>"
423                         if name[:3] == "e2:":
424                                 assert name == "e2:convert"
425                                 
426                                 ctype = attrs["type"]
427                                 
428                                         # TODO: we need something better here
429                                 if ctype[:4] == "web:": # for now
430                                         self.converter = eval(ctype[4:])
431                                 else:
432                                         try:
433                                                 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
434                                         except ImportError:
435                                                 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
436                                 self.sub = [ ]
437                         else:
438                                 self.sub.append(tag)
439                 elif self.mode == 3:
440                         assert name == "e2:item", "found %s instead of e2:item!" % name
441                         assert "name" in attrs, "e2:item must have a name= attribute!"
442                         filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4}[attrs.get("filter", "")]
443                         self.sub.append(ListItem(attrs["name"], filter))
444
445         def endElement(self, name):
446                 if name == "e2:screen":
447                         self.screen = None
448                         return
449
450                 tag = "</" + name + ">"
451                 if self.mode == 0:
452                         self.res.append(tag)
453                 elif self.mode == 2 and name[:3] != "e2:":
454                         self.sub.append(tag)
455                 elif self.mode == 2: # closed 'convert' -> sub
456                         if len(self.sub) == 1:
457                                 self.sub = self.sub[0]
458                         c = self.converter(self.sub)
459                         c.connect(self.source)
460                         self.source = c
461                         del self.sub
462                 elif self.mode == 1: # closed 'element'
463                         # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
464                         if not self.is_streaming:
465                                 c = OneTimeElement(self.source_id)
466                         else:
467                                 c = StreamingElement(self.source_id)
468                         
469                         c.connect(self.source)
470                         self.res.append(c)
471                         self.screen.renderer.append(c)
472                         del self.source
473
474                 if name[:3] == "e2:":
475                         self.mode -= 1
476
477         def processingInstruction(self, target, data):
478                 self.res.append('<?' + target + ' ' + data + '>')
479         
480         def characters(self, ch):
481                 ch = ch.encode('utf-8')
482                 if self.mode == 0:
483                         self.res.append(ch)
484                 elif self.mode == 2:
485                         self.sub.append(ch)
486         
487         def startEntity(self, name):
488                 self.res.append('&' + name + ';');
489
490         def execBegin(self):
491                 for screen in self.screens:
492                         screen.execBegin()
493
494         def cleanup(self):
495                 print "screen cleanup!"
496                 for screen in self.screens:
497                         screen.execEnd()
498                         screen.doClose()
499                 self.screens = [ ]
500
501 def renderPage(stream, path, req, session):
502         
503         # read in the template, create required screens
504         # we don't have persistense yet.
505         # if we had, this first part would only be done once.
506         handler = webifHandler(session,req)
507         parser = make_parser()
508         parser.setFeature(feature_namespaces, 0)
509         parser.setContentHandler(handler)
510         parser.parse(open(util.sibpath(__file__, path)))
511         
512         # by default, we have non-streaming pages
513         finish = True
514         
515         # first, apply "commands" (aka. URL argument)
516         for x in handler.res:
517                 if isinstance(x, Element):
518                         x.handleCommand(req.args)
519
520         handler.execBegin()
521
522         # now, we have a list with static texts mixed
523         # with non-static Elements.
524         # flatten this list, write into the stream.
525         for x in handler.res:
526                 if isinstance(x, Element):
527                         if isinstance(x, StreamingElement):
528                                 finish = False
529                                 x.setStream(stream)
530                         x.render(stream)
531                 else:
532                         stream.write(str(x))
533
534         def ping(s):
535                 from twisted.internet import reactor
536                 s.write("\n");
537                 reactor.callLater(3, ping, s)
538         
539         # if we met a "StreamingElement", there is at least one
540         # element which wants to output data more than once,
541         # i.e. on host-originated changes.
542         # in this case, don't finish yet, don't cleanup yet,
543         # but instead do that when the client disconnects.
544         if finish:
545                 handler.cleanup()
546                 stream.finish()
547         else:
548                 # ok.
549                 # you *need* something which constantly sends something in a regular interval,
550                 # in order to detect disconnected clients.
551                 # i agree that this "ping" sucks terrible, so better be sure to have something 
552                 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
553                 ping(stream)
554                 stream.closed_callback = lambda: handler.cleanup()