replace RUZEE.shadedborder with curvycorners.
[vuplus_dvbapp-plugin] / webinterface / src / webif.py
1 # -*- coding: UTF-8 -*-
2 Version = '$Header$';
3
4 # things to improve:
5 #       - better error handling
6 #       - use namespace parser
7
8 from Tools.Import import my_import
9
10 from Components.Sources.Source import ObsoleteSource
11 from Components.Converter.Converter import Converter
12 from Components.Element import Element
13
14 from xml.sax import make_parser
15 from xml.sax.handler import ContentHandler, feature_namespaces
16 from xml.sax.saxutils import escape as escape_xml
17 from twisted.python import util
18 from urllib2 import quote
19
20 #DO NOT REMOVE THIS IMPORT
21 #It IS used (dynamically)
22 from WebScreens import *
23 #DO NOT REMOVE THIS IMPORT
24
25
26 global screen_cache
27 screen_cache = {}
28
29 # The classes and Function in File handle all ScreenPage-based requests
30 # ScreenPages use enigma2 standard functionality to bring contents to a webfrontend
31 #
32 # Like Skins a ScreenPage can consist of several Elements and Converters
33
34 #===============================================================================
35 # OneTimeElement
36 #
37 # This is the Standard Element for Rendering a "standard" WebElement
38 #===============================================================================
39 class OneTimeElement(Element):
40         def __init__(self, id):
41                 Element.__init__(self)
42                 self.source_id = id
43
44         def handleCommand(self, args):
45                 if ',' in self.source_id:
46                         paramlist = self.source_id.split(",")
47                         list = {}
48                         for key in paramlist:
49                                 arg = args.get(key, ())
50                                 Len = len(arg)
51                                 if Len == 0:
52                                         list[key] = None
53                                 elif Len == 1:
54                                         list[key] = "".join(arg)
55                                 elif Len == 2:
56                                         list[key] = arg[0]
57                         self.source.handleCommand(list)
58                 else:
59                         for c in args.get(self.source_id, ()):
60                                 self.source.handleCommand(c)
61
62         def render(self, request):
63                 t = self.source.getHTML(self.source_id)
64                 request.write(t)
65
66         def execBegin(self):
67                 self.suspended = False
68
69         def execEnd(self):
70                 self.suspended = True
71
72         def onShow(self):
73                 pass
74
75         def onHide(self):
76                 pass
77
78         def destroy(self):
79                 pass
80
81 #===============================================================================
82 # MacroElement
83 #
84 # A MacroElement helps using OneTimeElements inside a (Simple)ListFiller Loop
85 #===============================================================================
86 class MacroElement(OneTimeElement):
87         def __init__(self, id, macro_dict, macro_name):
88                 OneTimeElement.__init__(self, id)
89                 self.macro_dict = macro_dict
90                 self.macro_name = macro_name
91
92         def render(self, request):
93                 self.macro_dict[self.macro_name] = self.source.getHTML(self.source_id)
94
95 #===============================================================================
96 # StreamingElement
97 #
98 # In difference to an OneTimeElement a StreamingElement sends an ongoing Stream
99 # of Data. The end of the Streaming is usually when the client disconnects
100 #===============================================================================
101 class StreamingElement(OneTimeElement):
102         def __init__(self, id):
103                 OneTimeElement.__init__(self, id)
104                 self.request = None
105
106         def changed(self, what):
107                 if self.request:
108                         self.render(self.request)
109
110         def setRequest(self, request):
111                 self.request = request
112
113 #===============================================================================
114 # ListItem
115 #
116 # a to-be-filled list item
117 #===============================================================================
118 class ListItem:
119         def __init__(self, name, filternum):
120                 self.name = name
121                 self.filternum = filternum
122
123 #===============================================================================
124 # ListMacroItem
125 #
126 # MacroItem inside a (Simple)ListFiller
127 #===============================================================================
128 class ListMacroItem:
129         def __init__(self, macrodict, macroname):
130                 self.macrodict = macrodict
131                 self.macroname = macroname
132
133
134 #===============================================================================
135 # TextToHTML
136 #
137 # Returns the String as is
138 #===============================================================================
139 class TextToHTML(Converter):
140         def __init__(self, arg):
141                 Converter.__init__(self, arg)
142
143         def getHTML(self, id):
144                 return self.source.text.replace('\xc2\x86', '').replace('\xc2\x87', '').decode("utf-8", "ignore").encode("utf-8") # encode & etc. here!
145
146 #===============================================================================
147 # TextToXML
148 #
149 # Escapes the given Text to be XML conform
150 #===============================================================================
151 class TextToXML(Converter):
152         def __init__(self, arg):
153                 Converter.__init__(self, arg)
154
155         def getHTML(self, id):
156                 return escape_xml(self.source.text).replace('\xc2\x86', '').replace('\xc2\x87', '').replace("\x19", "").replace("\x1c", "").replace("\x1e", "").decode("utf-8", "ignore").encode("utf-8")
157
158 #===============================================================================
159 # TextToURL
160 #
161 # Escapes the given Text so it can be used inside a URL
162 #===============================================================================
163 class TextToURL(Converter):
164         def __init__(self, arg):
165                 Converter.__init__(self, arg)
166
167         def getHTML(self, id):
168                 return self.source.text.replace(" ", "%20").replace("+", "%2b").replace("&", "%26").replace('\xc2\x86', '').replace('\xc2\x87', '').decode("utf-8", "ignore").encode("utf-8")
169
170 #===============================================================================
171 # ReturnEmptyXML
172
173 # Returns a XML only consisting of <rootElement />
174 #===============================================================================
175 class ReturnEmptyXML(Converter):
176         def __init__(self, arg):
177                 Converter.__init__(self, arg)
178
179         def getHTML(self, id):
180                 return "<rootElement />"
181
182 #===============================================================================
183 # Null
184 # Return simply NOTHING
185 # Useful if you only want to issue a command.
186 #===============================================================================
187 class Null(Converter):
188         def __init__(self, arg):
189                 Converter.__init__(self, arg)
190
191         def getHTML(self, id):
192                 return ""
193
194
195 #===============================================================================
196 # JavascriptUpdate
197 #
198 # Transforms a string into a javascript update pattern
199 #===============================================================================
200 class JavascriptUpdate(Converter):
201         def __init__(self, arg):
202                 Converter.__init__(self, arg)
203
204         def getHTML(self, id):
205                 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
206                 #                all other will replace this in JS
207                 return '<script>parent.set("%s", "%s");</script>\n' % (id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '&deg;'))
208
209 #===============================================================================
210 # SimpleListFiller
211 #
212 # The performant 'one-dimensonial listfiller' engine (podlfe)
213 #===============================================================================
214 class SimpleListFiller(Converter):
215         def __init__(self, arg):
216                 Converter.__init__(self, arg)
217                 
218         def getText(self):
219                 l = self.source.simplelist
220                 conv_args = self.converter_arguments            
221                 
222                 list = [ ]
223                 for element in conv_args:
224                         if isinstance(element, basestring):
225                                 list.append((element, None))
226                         elif isinstance(element, ListItem):
227                                 list.append((element, element.filternum))
228                         elif isinstance(element, ListMacroItem):
229                                 list.append(element.macrodict[element.macroname], None)
230                         else:
231                                 raise Exception("neither string, ListItem nor ListMacroItem")
232                         
233                 strlist = [ ]
234                 append = strlist.append
235                 for item in l:
236                         if item is None:
237                                 item = ""
238                                 
239                         for (element, filternum) in list:
240                                 #filter out "non-displayable" Characters - at the very end, do it the hard way...
241                                 item = str(item).replace('\xc2\x86', '').replace('\xc2\x87', '').replace("\x19", "").replace("\x1c", "").replace("\x1e", "").decode("utf-8", "ignore").encode("utf-8")
242                                 
243                                 if not filternum:
244                                         append(element)
245                                 elif filternum == 2:
246                                         append(item.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
247                                 elif filternum == 3:                                    
248                                         append(escape_xml(item))
249                                 elif filternum == 4:
250                                         append(item.replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
251                                 elif filternum == 5:
252                                         append(quote(item))
253                                 elif filternum == 6:
254                                         time = parseint(item) or 0
255                                         t = localtime(time)
256                                         append("%02d:%02d" % (t.tm_hour, t.tm_min))
257                                 elif filternum == 7:
258                                         time = parseint(item) or 0
259                                         t = localtime(time)
260                                         append("%d min" % (time / 60))
261                                 else:
262                                         append(item)
263                 # (this will be done in c++ later!)
264
265                 return ''.join(strlist)         
266         
267         text = property(getText)
268                         
269 #===============================================================================
270 # the performant 'listfiller'-engine (plfe)
271 #===============================================================================
272 class ListFiller(Converter):
273         def __init__(self, arg):
274                 Converter.__init__(self, arg)
275 #               print "ListFiller-arg: ",arg
276
277         def getText(self):
278                 l = self.source.list
279                 lut = self.source.lut
280                 conv_args = self.converter_arguments
281
282                 # now build a ["string", 1, "string", 2]-styled list, with indices into the
283                 # list to avoid lookup of item name for each entry
284                 lutlist = [ ]
285                 for element in conv_args:
286                         if isinstance(element, basestring):
287                                 lutlist.append((element, None))
288                         elif isinstance(element, ListItem):
289                                 lutlist.append((lut[element.name], element.filternum))
290                         elif isinstance(element, ListMacroItem):
291                                 lutlist.append((element.macrodict[element.macroname], None))
292                         else:
293                                 raise Exception("neither string, ListItem nor ListMacroItem")
294
295                 # now, for the huge list, do:
296                 strlist = [ ]
297                 append = strlist.append
298                 for item in l:
299                         for (element, filternum) in lutlist:                    
300                                 #None becomes ""
301                                 curitem = ""
302                                 if filternum:
303                                         #filter out "non-displayable" Characters - at the very end, do it the hard way...
304                                         curitem = str(item[element]).replace('\xc2\x86', '').replace('\xc2\x87', '').replace("\x19", "").replace("\x1c", "").replace("\x1e", "").decode("utf-8", "ignore").encode("utf-8")
305                                         if curitem is None:
306                                                 curitem = ""
307                                 else:
308                                         if element is None:
309                                                 element = ""
310                                                 
311                                 if not filternum:
312                                         append(element)
313                                 elif filternum == 2:
314                                         append(curitem.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
315                                 elif filternum == 3:
316                                         append( escape_xml( curitem ))
317                                 elif filternum == 4:
318                                         append(curitem.replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
319                                 elif filternum == 5:
320                                         append(quote(curitem))
321                                 elif filternum == 6:
322                                         from time import localtime
323                                         time = int(float(curitem)) or 0
324                                         t = localtime(time)
325                                         append("%02d:%02d" % (t.tm_hour, t.tm_min))
326                                 elif filternum == 7:
327                                         from time import localtime
328                                         time = int(float(curitem)) or 0
329                                         t = localtime(time)
330                                         append("%d min" % (time / 60))                                  
331                                 else:
332                                         append(curitem)
333                 # (this will be done in c++ later!)
334
335                 return ''.join(strlist)
336
337         text = property(getText)
338
339 #===============================================================================
340 # webifHandler
341 #
342 # Handles the Content of a Web-Request
343 # It looks up the source, instantiates the Element and Calls the Converter
344 #===============================================================================
345 class webifHandler(ContentHandler):
346         def __init__(self, session, request):
347                 self.res = [ ]
348                 self.mode = 0
349                 self.screen = None
350                 self.session = session
351                 self.screens = [ ]
352                 self.request = request
353                 self.macros = { }
354
355         def start_element(self, attrs):
356                 scr = self.screen
357
358                 wsource = attrs["source"]
359
360                 path = wsource.split('.')
361                 while len(path) > 1:
362                         scr = self.screen.getRelatedScreen(path[0])
363                         if scr is None:
364                                 print "[webif.py] Parent Screen not found!"
365                                 print wsource
366                         path = path[1:]
367
368                 source = scr.get(path[0])
369
370                 if isinstance(source, ObsoleteSource):
371                         # however, if we found an "obsolete source", issue warning, and resolve the real source.
372                         print "WARNING: WEBIF '%s' USES OBSOLETE SOURCE '%s', USE '%s' INSTEAD!" % (name, wsource, source.new_source)
373                         print "OBSOLETE SOURCE WILL BE REMOVED %s, PLEASE UPDATE!" % (source.removal_date)
374                         if source.description:
375                                 print source.description
376
377                         wsource = source.new_source
378                 else:
379                         pass
380                         # otherwise, use that source.
381
382                 self.source = source
383                 self.source_id = str(attrs.get("id", wsource))
384                 self.is_streaming = "streaming" in attrs
385                 self.macro_name = attrs.get("macro") or None
386
387         def end_element(self):
388                 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
389                 if not self.is_streaming:
390                         if self.macro_name is None:
391                                 c = OneTimeElement(self.source_id)
392                         else:
393                                 c = MacroElement(self.source_id, self.macros, self.macro_name)
394                 else:
395                         assert self.macro_name is None
396                         c = StreamingElement(self.source_id)
397
398                 c.connect(self.source)
399                 self.res.append(c)
400                 self.screen.renderer.append(c)
401                 del self.source
402
403         def start_convert(self, attrs):
404                 ctype = attrs["type"]
405
406                 # TODO: we need something better here
407                 if ctype[:4] == "web:": # for now
408                         self.converter = eval(ctype[4:])
409                 else:
410                         try:
411                                 self.converter = my_import('.'.join(("Components", "Converter", ctype))).__dict__.get(ctype)
412                         except ImportError:
413                                 self.converter = my_import('.'.join(("Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype))).__dict__.get(ctype)
414                 self.sub = [ ]
415
416         def end_convert(self):
417                 if len(self.sub) == 1:
418                         self.sub = self.sub[0]
419                 c = self.converter(self.sub)
420                 c.connect(self.source)
421                 self.source = c
422                 del self.sub
423
424         def parse_item(self, attrs):
425                 if "name" in attrs:
426                         filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4, "urlencode": 5, "time": 6, "minutes": 7}[attrs.get("filter", "")]
427                         self.sub.append(ListItem(attrs["name"], filter))
428                 else:
429                         assert "macro" in attrs, "e2:item must have a name= or macro= attribute!"
430                         self.sub.append(ListMacroItem(self.macros, attrs["macro"]))
431
432         def startElement(self, name, attrs):
433                 if name == "e2:screen":
434                         self.screen = eval(attrs["name"])(self.session, self.request) # fixme
435                         self.screens.append(self.screen)
436                         return
437
438                 if name[:3] == "e2:":
439                         self.mode += 1
440
441                 tag = '<' + name + ''.join([' %s="%s"' % x for x in attrs.items()]) + '>'
442                 #tag = tag.encode('utf-8')
443
444                 if self.mode == 0:
445                         self.res.append(tag)
446                 elif self.mode == 1: # expect "<e2:element>"
447                         assert name == "e2:element", "found %s instead of e2:element" % name
448                         self.start_element(attrs)
449                 elif self.mode == 2: # expect "<e2:convert>"
450                         if name[:3] == "e2:":
451                                 assert name == "e2:convert"
452                                 self.start_convert(attrs)
453                         else:
454                                 self.sub.append(tag)
455                 elif self.mode == 3:
456                         assert name == "e2:item", "found %s instead of e2:item!" % name
457
458                         self.parse_item(attrs)
459
460         def endElement(self, name):
461                 if name == "e2:screen":
462                         self.screen = None
463                         return
464
465                 tag = "</" + name + ">"
466                 if self.mode == 0:
467                         self.res.append(tag)
468                 elif self.mode == 2 and name[:3] != "e2:":
469                         self.sub.append(tag)
470                 elif self.mode == 2: # closed 'convert' -> sub
471                         self.end_convert()
472                 elif self.mode == 1: # closed 'element'
473                         self.end_element()
474                 if name[:3] == "e2:":
475                         self.mode -= 1
476
477         def processingInstruction(self, target, data):
478                 self.res.append('<?' + target + ' ' + data + '>')
479
480         def characters(self, ch):
481                 ch = ch.encode('utf-8')
482                 if self.mode == 0:
483                         self.res.append(ch)
484                 elif self.mode == 2:
485                         self.sub.append(ch)
486
487         def startEntity(self, name):
488                 self.res.append('&' + name + ';');
489
490         def execBegin(self):
491                 for screen in self.screens:
492                         screen.execBegin()
493
494         def cleanup(self):
495                 print "screen cleanup!"
496                 for screen in self.screens:
497                         screen.execEnd()
498                         screen.doClose()
499                 self.screens = [ ]
500
501 #===============================================================================
502 # renderPage
503 #
504 # Creates the Handler for a Request and calls it
505 # Also ensures that the Handler is finished after the Request is done
506 #===============================================================================
507 def renderPage(request, path, session):
508         # read in the template, create required screens
509         # we don't have persistense yet.
510         # if we had, this first part would only be done once.
511         handler = webifHandler(session, request)
512         parser = make_parser()
513         parser.setFeature(feature_namespaces, 0)
514         parser.setContentHandler(handler)
515         parser.parse(open(util.sibpath(__file__, path)))
516
517         # by default, we have non-streaming pages
518         finish = True
519
520         # first, apply "commands" (aka. URL argument)
521         for x in handler.res:
522                 if isinstance(x, Element):
523                         x.handleCommand(request.args)
524
525         handler.execBegin()
526
527         # now, we have a list with static texts mixed
528         # with non-static Elements.
529         # flatten this list, write into the request.
530         for x in handler.res:
531                 if isinstance(x, Element):
532                         if isinstance(x, StreamingElement):
533                                 finish = False
534                                 x.setRequest(request)
535                         x.render(request)
536                 else:
537                         request.write(str(x))
538
539         # if we met a "StreamingElement", there is at least one
540         # element which wants to output data more than once,
541         # i.e. on host-originated changes.
542         # in this case, don't finish yet, don't cleanup yet,
543         # but instead do that when the client disconnects.
544         if finish:
545                 requestFinish(handler, request)
546         
547         else:   
548                 def requestFinishDeferred(nothing, handler, request):
549                         from twisted.internet import reactor
550                         reactor.callLater(0, requestFinish, handler, request)                           
551                 
552                 d = request.notifyFinish()
553
554                 d.addBoth( requestFinishDeferred, handler, request )
555                                                         
556 #===============================================================================
557 # requestFinish
558 #
559 # This has to be/is called at the end of every ScreenPage-based Request
560 #===============================================================================
561 def requestFinish(handler, request):
562         handler.cleanup()
563         request.finish()        
564         
565         del handler