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