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