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