whitespace fix
[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.Source import Source, ObsoleteSource
19
20 from Components.Sources.Clock import Clock
21 from Components.Sources.ServiceList import ServiceList
22
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
40
41 from Components.Sources.FrontendStatus import FrontendStatus
42
43 from Components.Converter.Converter import Converter
44
45 from Components.Element import Element
46
47 from xml.sax import make_parser
48 from xml.sax.handler import ContentHandler, feature_namespaces
49
50 from twisted.python import util
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,func = Message.PRINT)
83                 self["GetAnswer"] = Message(session,func = Message.ANSWER)
84
85 class AudioWebScreen(WebScreen):
86         def __init__(self, session,request):
87                 WebScreen.__init__(self, session,request)
88                 self["AudioTracks"] = AudioTracks(session)              
89
90 class AboutWebScreen(WebScreen):
91         def __init__(self, session,request):
92                 WebScreen.__init__(self, session,request)
93                 self["About"] = About(session)
94                 
95 class VolumeWebScreen(WebScreen):
96         def __init__(self, session,request):
97                 WebScreen.__init__(self, session,request)
98                 self["Volume"] = Volume(session)
99
100 class SettingsWebScreen(WebScreen):
101         def __init__(self, session,request):
102                 WebScreen.__init__(self, session,request)
103                 self["Settings"] = Settings(session)
104
105 class SubServiceWebScreen(WebScreen):
106         def __init__(self, session,request):
107                 WebScreen.__init__(self, session,request)
108                 self["SubServices"] = SubServices(session)
109
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)
117
118         def getServiceList(self, sRef):
119                 self["ServiceList"].root = sRef
120
121         def zapTo(self, reftozap):
122                 from Components.config import config
123                 pc = config.ParentalControl.configured.value
124                 if pc:
125                         config.ParentalControl.configured.value = False
126                 self.session.nav.playService(reftozap)
127                 if pc:
128                         config.ParentalControl.configured.value = pc
129                 """
130                 switching config.ParentalControl.configured.value
131                 ugly, but necessary :(
132                 """
133
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)
140
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)
150
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)
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                         scr = self.screen
425                         
426                         assert name == "e2:element", "found %s instead of e2:element" % name
427                         wsource = attrs["source"]
428                         
429                         path = wsource.split('.')
430                         while len(path) > 1:
431                                 scr = self.screen.getRelatedScreen(path[0])
432                                 if scr is None:
433                                         print "[webif.py] Parent Screen not found!"
434                                         print wsource
435                                 path = path[1:]
436                         
437                         source = scr.get(path[0])
438
439                         if isinstance(source, ObsoleteSource):
440                                 # however, if we found an "obsolete source", issue warning, and resolve the real source.
441                                 print "WARNING: WEBIF '%s' USES OBSOLETE SOURCE '%s', USE '%s' INSTEAD!" % (name, wsource, source.new_source)
442                                 print "OBSOLETE SOURCE WILL BE REMOVED %s, PLEASE UPDATE!" % (source.removal_date)
443                                 if source.description:
444                                         print source.description
445
446                                 wsource = source.new_source
447                         else:
448                                 pass
449                                 # otherwise, use that source.
450                                                 
451                         self.source = source            
452                         self.source_id = str(attrs.get("id", wsource))
453                         self.is_streaming = "streaming" in attrs
454                         
455                 elif self.mode == 2: # expect "<e2:convert>"
456                         if name[:3] == "e2:":
457                                 assert name == "e2:convert"
458                                 
459                                 ctype = attrs["type"]
460                                 
461                                         # TODO: we need something better here
462                                 if ctype[:4] == "web:": # for now
463                                         self.converter = eval(ctype[4:])
464                                 else:
465                                         try:
466                                                 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
467                                         except ImportError:
468                                                 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
469                                 self.sub = [ ]
470                         else:
471                                 self.sub.append(tag)
472                 elif self.mode == 3:
473                         assert name == "e2:item", "found %s instead of e2:item!" % name
474                         assert "name" in attrs, "e2:item must have a name= attribute!"
475                         filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4}[attrs.get("filter", "")]
476                         self.sub.append(ListItem(attrs["name"], filter))
477
478         def endElement(self, name):
479                 if name == "e2:screen":
480                         self.screen = None
481                         return
482
483                 tag = "</" + name + ">"
484                 if self.mode == 0:
485                         self.res.append(tag)
486                 elif self.mode == 2 and name[:3] != "e2:":
487                         self.sub.append(tag)
488                 elif self.mode == 2: # closed 'convert' -> sub
489                         if len(self.sub) == 1:
490                                 self.sub = self.sub[0]
491                         c = self.converter(self.sub)
492                         c.connect(self.source)
493                         self.source = c
494                         del self.sub
495                 elif self.mode == 1: # closed 'element'
496                         # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
497                         if not self.is_streaming:
498                                 c = OneTimeElement(self.source_id)
499                         else:
500                                 c = StreamingElement(self.source_id)
501                         
502                         c.connect(self.source)
503                         self.res.append(c)
504                         self.screen.renderer.append(c)
505                         del self.source
506
507                 if name[:3] == "e2:":
508                         self.mode -= 1
509
510         def processingInstruction(self, target, data):
511                 self.res.append('<?' + target + ' ' + data + '>')
512         
513         def characters(self, ch):
514                 ch = ch.encode('utf-8')
515                 if self.mode == 0:
516                         self.res.append(ch)
517                 elif self.mode == 2:
518                         self.sub.append(ch)
519         
520         def startEntity(self, name):
521                 self.res.append('&' + name + ';');
522
523         def execBegin(self):
524                 for screen in self.screens:
525                         screen.execBegin()
526
527         def cleanup(self):
528                 print "screen cleanup!"
529                 for screen in self.screens:
530                         screen.execEnd()
531                         screen.doClose()
532                 self.screens = [ ]
533
534 def renderPage(stream, path, req, session):
535         
536         # read in the template, create required screens
537         # we don't have persistense yet.
538         # if we had, this first part would only be done once.
539         handler = webifHandler(session,req)
540         parser = make_parser()
541         parser.setFeature(feature_namespaces, 0)
542         parser.setContentHandler(handler)
543         parser.parse(open(util.sibpath(__file__, path)))
544         
545         # by default, we have non-streaming pages
546         finish = True
547         
548         # first, apply "commands" (aka. URL argument)
549         for x in handler.res:
550                 if isinstance(x, Element):
551                         x.handleCommand(req.args)
552
553         handler.execBegin()
554
555         # now, we have a list with static texts mixed
556         # with non-static Elements.
557         # flatten this list, write into the stream.
558         for x in handler.res:
559                 if isinstance(x, Element):
560                         if isinstance(x, StreamingElement):
561                                 finish = False
562                                 x.setStream(stream)
563                         x.render(stream)
564                 else:
565                         stream.write(str(x))
566
567         def ping(s):
568                 from twisted.internet import reactor
569                 s.write("\n");
570                 reactor.callLater(3, ping, s)
571         
572         # if we met a "StreamingElement", there is at least one
573         # element which wants to output data more than once,
574         # i.e. on host-originated changes.
575         # in this case, don't finish yet, don't cleanup yet,
576         # but instead do that when the client disconnects.
577         if finish:
578                 handler.cleanup()
579                 stream.finish()
580         else:
581                 # ok.
582                 # you *need* something which constantly sends something in a regular interval,
583                 # in order to detect disconnected clients.
584                 # i agree that this "ping" sucks terrible, so better be sure to have something 
585                 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
586                 ping(stream)
587                 stream.closed_callback = lambda: handler.cleanup()