add Movietag List avaible with http://dm7025/web/movietags
[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
12 from Screens.Screen import Screen
13 from Tools.Import import my_import
14
15 # for our testscreen
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
36 from Components.Sources.FrontendStatus import FrontendStatus
37
38 from Components.Converter.Converter import Converter
39
40 from Components.Element import Element
41
42 from xml.sax import make_parser
43 from xml.sax.handler import ContentHandler, feature_namespaces
44
45 from twisted.python import util
46
47 import sys
48 import time
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 # a test screen
60 class TestScreen(InfoBarServiceName, InfoBarEvent,InfoBarTuner, WebScreen):
61         def __init__(self, session,request):
62                 WebScreen.__init__(self, session,request)
63                 InfoBarServiceName.__init__(self)
64                 InfoBarEvent.__init__(self)
65                 InfoBarTuner.__init__(self)
66                 self["CurrentTime"] = Clock()
67 #               self["TVSystem"] = Config(config.av.tvsystem)
68 #               self["OSDLanguage"] = Config(config.osd.language)
69 #               self["FirstRun"] = Config(config.misc.firstrun)
70                 from enigma import eServiceReference
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                 self["SwitchService"] = ServiceList(fav, command_func = self.zapTo, validate_commands=False)
73                 self["ServiceList"] = ServiceList(fav, command_func = self.getServiceList, validate_commands=False)
74                 self["ServiceListRecursive"] = ServiceListRecursive(session, func=ServiceListRecursive.FETCH)
75                 self["ParentControlList"] = ParentControl(session)
76                 self["SubServices"] = SubServices(session)
77                 self["Volume"] = Volume(session)
78                 self["EPGTITLE"] = EPG(session,func=EPG.TITLE)
79                 self["EPGSERVICE"] = EPG(session,func=EPG.SERVICE)
80                 self["EPGNOW"] = EPG(session,func=EPG.NOW)
81                 self["TimerList"] = Timer(session,func = Timer.LIST)
82                 self["TimerAddEventID"] = Timer(session,func = Timer.ADDBYID)
83                 self["TimerAdd"] = Timer(session,func = Timer.ADD)
84                 self["TimerDel"] = Timer(session,func = Timer.DEL)
85                 self["TimerChange"] = Timer(session,func = Timer.CHANGE)
86                 self["TimerListWrite"] = Timer(session,func = Timer.WRITE)
87                 self["TVBrowser"] = Timer(session,func = Timer.TVBROWSER)
88                 self["RecordNow"] = Timer(session,func = Timer.RECNOW)
89                 self["MovieList"] = Movie(session,func = Movie.LIST)
90                 self["MovieFileDel"] = Movie(session,func = Movie.DEL)
91                 self["MovieTags"] = Movie(session,func = Movie.TAGS)
92                 self["Volume"] = Volume(session)
93                 self["Message"] = Message(session)
94                 self["PowerState"] = PowerState(session)
95                 self["RemoteControl"] = RemoteControl(session)
96                 self["Settings"] = Settings(session)
97                 self["AudioTracks"] = AudioTracks(session)
98                 
99                 self["About"] = About(session)
100                 
101         def getServiceList(self, sRef):
102                 self["ServiceList"].root = sRef
103
104         def zapTo(self, reftozap):
105                 from Components.config import config
106                 pc = config.ParentalControl.configured.value
107                 if pc:
108                         config.ParentalControl.configured.value = False
109                 self.session.nav.playService(reftozap)
110                 if pc:
111                         config.ParentalControl.configured.value = pc
112                 """
113                 switching config.ParentalControl.configured.value
114                 ugly, but necessary :(
115                 """
116
117 # TODO: (really.) put screens into own files.
118 class Streaming(WebScreen):
119         def __init__(self, session,request):
120                 WebScreen.__init__(self, session,request)
121                 from Components.Sources.StreamService import StreamService
122                 self["StreamService"] = StreamService(self.session.nav)
123
124 class StreamingM3U(WebScreen):
125         def __init__(self, session,request):
126                 WebScreen.__init__(self, session,request)
127                 from Components.Sources.StaticText import StaticText
128                 from Components.Sources.Config import Config
129                 from Components.config import config
130                 self["ref"] = StaticText()
131                 self["localip"] = RequestData(request,what=RequestData.HOST)
132
133 class TsM3U(WebScreen):
134         def __init__(self, session,request):
135                 WebScreen.__init__(self, session,request)
136                 from Components.Sources.StaticText import StaticText
137                 from Components.Sources.Config import Config
138                 from Components.config import config
139                 self["file"] = StaticText()
140                 self["localip"] = RequestData(request,what=RequestData.HOST)
141
142 class RestartTwisted(WebScreen):
143         def __init__(self, session,request):
144                 WebScreen.__init__(self, session,request)
145                 import plugin
146                 plugin.restartWebserver()
147                 
148 class GetPid(WebScreen):
149       def __init__(self, session,request):
150          WebScreen.__init__(self, session,request)
151          from Components.Sources.StaticText import StaticText
152          from enigma import iServiceInformation
153          pids = self.session.nav.getCurrentService()
154          if pids is not None:
155                  pidinfo = pids.info()
156                  VPID = hex(pidinfo.getInfo(iServiceInformation.sVideoPID))
157                  APID = hex(pidinfo.getInfo(iServiceInformation.sAudioPID))
158                  PPID = hex(pidinfo.getInfo(iServiceInformation.sPMTPID))
159          self["pids"] = StaticText("%s,%s,%s"%(PPID.lstrip("0x"),VPID.lstrip("0x"),APID.lstrip("0x")))
160          self["localip"] = RequestData(request,what=RequestData.HOST)
161
162
163 # implements the 'render'-call.
164 # this will act as a downstream_element, like a renderer.
165 class OneTimeElement(Element):
166         def __init__(self, id):
167                 Element.__init__(self)
168                 self.source_id = id
169
170         # CHECKME: is this ok performance-wise?
171         def handleCommand(self, args):
172                 if self.source_id.find(",") >=0:
173                         paramlist = self.source_id.split(",")
174                         list={}
175                         for key in paramlist:
176                                 arg = args.get(key, [])
177                                 if len(arg) == 0:
178                                         list[key] = None        
179                                 elif len(arg) == 1:
180                                         list[key] = "".join(arg)        
181                                 elif len(arg) == 2:
182                                         list[key] = arg[0]
183                         self.source.handleCommand(list)
184                 else:
185                         for c in args.get(self.source_id, []):
186                                 self.source.handleCommand(c)
187                 
188         def render(self, stream):
189                 t = self.source.getHTML(self.source_id)
190                 stream.write(t)
191
192         def execBegin(self):
193                 pass
194         
195         def execEnd(self):
196                 pass
197         
198         def onShow(self):
199                 pass
200
201         def onHide(self):
202                 pass
203         
204         def destroy(self):
205                 pass
206
207 class StreamingElement(OneTimeElement):
208         def __init__(self, id):
209                 OneTimeElement.__init__(self, id)
210                 self.stream = None
211
212         def changed(self, what):
213                 if self.stream:
214                         self.render(self.stream)
215
216         def setStream(self, stream):
217                 self.stream = stream
218
219 # a to-be-filled list item
220 class ListItem:
221         def __init__(self, name, filternum):
222                 self.name = name
223                 self.filternum = filternum
224         
225 class TextToHTML(Converter):
226         def __init__(self, arg):
227                 Converter.__init__(self, arg)
228
229         def getHTML(self, id):
230                 return self.source.text # encode & etc. here!
231
232 class TextToURL(Converter):
233         def __init__(self, arg):
234                 Converter.__init__(self, arg)
235
236         def getHTML(self, id):
237                 return self.source.text.replace(" ","%20")
238
239 class ReturnEmptyXML(Converter):
240         def __init__(self, arg):
241                 Converter.__init__(self, arg)
242                 
243         def getHTML(self, id):
244                 return "<rootElement></rootElement>"
245
246 # a null-output. Useful if you only want to issue a command.
247 class Null(Converter):
248         def __init__(self, arg):
249                 Converter.__init__(self, arg)
250
251         def getHTML(self, id):
252                 return ""
253
254 class JavascriptUpdate(Converter):
255         def __init__(self, arg):
256                 Converter.__init__(self, arg)
257
258         def getHTML(self, id):
259                 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
260                 #                all other will replace this in JS
261                 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '&deg;'))
262
263 # the performant 'listfiller'-engine (plfe)
264 class ListFiller(Converter):
265         def __init__(self, arg):
266                 Converter.__init__(self, arg)
267
268         def getText(self):
269                 l = self.source.list
270                 lut = self.source.lut
271                 conv_args = self.converter_arguments
272
273                 # now build a ["string", 1, "string", 2]-styled list, with indices into the
274                 # list to avoid lookup of item name for each entry
275                 lutlist = [ isinstance(element, basestring) and (element, None) or (lut[element.name], element.filternum) for element in conv_args ]
276
277                 # now, for the huge list, do:
278                 strlist = [ ]
279                 append = strlist.append
280                 for item in l:
281                         for (element, filternum) in lutlist:
282                                 if not filternum:
283                                         append(element)
284                                 elif filternum == 2:
285                                         append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
286                                 elif filternum == 3:
287                                         append(str(item[element]).replace("&", "&amp;").replace("<", "&lt;").replace('"', '&quot;').replace(">", "&gt;"))
288                                 elif filternum == 4:
289                                         append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
290                                 else:
291                                         append(str(item[element]))
292                 # (this will be done in c++ later!)
293                 return ''.join(strlist)
294
295         text = property(getText)
296
297 class webifHandler(ContentHandler):
298         def __init__(self, session,request):
299                 self.res = [ ]
300                 self.mode = 0
301                 self.screen = None
302                 self.session = session
303                 self.screens = [ ]
304                 self.request = request
305         
306         def startElement(self, name, attrs):
307                 if name == "e2:screen":
308                         self.screen = eval(attrs["name"])(self.session,self.request) # fixme
309                         self.screens.append(self.screen)
310                         return
311         
312                 if name[:3] == "e2:":
313                         self.mode += 1
314
315                 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
316                 tag.insert(0, name)
317                 tag.insert(0, '<')
318                 tag.append('>')
319                 tag = ''.join(tag)#.encode('utf-8')
320
321                 if self.mode == 0:
322                         self.res.append(tag)
323                 elif self.mode == 1: # expect "<e2:element>"
324                         assert name == "e2:element", "found %s instead of e2:element" % name
325                         source = attrs["source"]
326                         self.source_id = str(attrs.get("id", source))
327                         self.source = self.screen[source]
328                         self.is_streaming = "streaming" in attrs
329                 elif self.mode == 2: # expect "<e2:convert>"
330                         if name[:3] == "e2:":
331                                 assert name == "e2:convert"
332                                 
333                                 ctype = attrs["type"]
334                                 
335                                         # TODO: we need something better here
336                                 if ctype[:4] == "web:": # for now
337                                         self.converter = eval(ctype[4:])
338                                 else:
339                                         try:
340                                                 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
341                                         except ImportError:
342                                                 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
343                                 self.sub = [ ]
344                         else:
345                                 self.sub.append(tag)
346                 elif self.mode == 3:
347                         assert name == "e2:item", "found %s instead of e2:item!" % name
348                         assert "name" in attrs, "e2:item must have a name= attribute!"
349                         filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4}[attrs.get("filter", "")]
350                         self.sub.append(ListItem(attrs["name"], filter))
351
352         def endElement(self, name):
353                 if name == "e2:screen":
354                         self.screen = None
355                         return
356
357                 tag = "</" + name + ">"
358                 if self.mode == 0:
359                         self.res.append(tag)
360                 elif self.mode == 2 and name[:3] != "e2:":
361                         self.sub.append(tag)
362                 elif self.mode == 2: # closed 'convert' -> sub
363                         if len(self.sub) == 1:
364                                 self.sub = self.sub[0]
365                         c = self.converter(self.sub)
366                         c.connect(self.source)
367                         self.source = c
368                         del self.sub
369                 elif self.mode == 1: # closed 'element'
370                         # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
371                         if not self.is_streaming:
372                                 c = OneTimeElement(self.source_id)
373                         else:
374                                 c = StreamingElement(self.source_id)
375                         
376                         c.connect(self.source)
377                         self.res.append(c)
378                         self.screen.renderer.append(c)
379                         del self.source
380
381                 if name[:3] == "e2:":
382                         self.mode -= 1
383
384         def processingInstruction(self, target, data):
385                 self.res.append('<?' + target + ' ' + data + '>')
386         
387         def characters(self, ch):
388                 ch = ch.encode('utf-8')
389                 if self.mode == 0:
390                         self.res.append(ch)
391                 elif self.mode == 2:
392                         self.sub.append(ch)
393         
394         def startEntity(self, name):
395                 self.res.append('&' + name + ';');
396
397         def execBegin(self):
398                 for screen in self.screens:
399                         screen.execBegin()
400
401         def cleanup(self):
402                 print "screen cleanup!"
403                 for screen in self.screens:
404                         screen.execEnd()
405                         screen.doClose()
406                 self.screens = [ ]
407
408 def renderPage(stream, path, req, session):
409         
410         # read in the template, create required screens
411         # we don't have persistense yet.
412         # if we had, this first part would only be done once.
413         handler = webifHandler(session,req)
414         parser = make_parser()
415         parser.setFeature(feature_namespaces, 0)
416         parser.setContentHandler(handler)
417         parser.parse(open(util.sibpath(__file__, path)))
418         
419         # by default, we have non-streaming pages
420         finish = True
421         
422         # first, apply "commands" (aka. URL argument)
423         for x in handler.res:
424                 if isinstance(x, Element):
425                         x.handleCommand(req.args)
426
427         handler.execBegin()
428
429         # now, we have a list with static texts mixed
430         # with non-static Elements.
431         # flatten this list, write into the stream.
432         for x in handler.res:
433                 if isinstance(x, Element):
434                         if isinstance(x, StreamingElement):
435                                 finish = False
436                                 x.setStream(stream)
437                         x.render(stream)
438                 else:
439                         stream.write(str(x))
440
441         def ping(s):
442                 from twisted.internet import reactor
443                 s.write("\n");
444                 reactor.callLater(3, ping, s)
445         
446         # if we met a "StreamingElement", there is at least one
447         # element which wants to output data more than once,
448         # i.e. on host-originated changes.
449         # in this case, don't finish yet, don't cleanup yet,
450         # but instead do that when the client disconnects.
451         if finish:
452                 handler.cleanup()
453                 stream.finish()
454         else:
455                 # ok.
456                 # you *need* something which constantly sends something in a regular interval,
457                 # in order to detect disconnected clients.
458                 # i agree that this "ping" sucks terrible, so better be sure to have something 
459                 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
460                 ping(stream)
461                 stream.closed_callback = lambda: handler.cleanup()