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