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