nothing
[vuplus_dvbapp-plugin] / webinterface / src / webif.py
index fb49005..97e4bdd 100644 (file)
+# -*- coding: UTF-8 -*-
 Version = '$Header$';
 
-# OK, this is more than a proof of concept
 # things to improve:
-#  - nicer code
-#  - screens need to be defined somehow else. 
-#    I don't know how, yet. Probably each in an own file.
-#  - more components, like the channellist
-#  - better error handling
-#  - use namespace parser
-
-from Screens.Screen import Screen
-from Tools.Import import my_import
-
-# for our testscreen
-from Screens.InfoBarGenerics import InfoBarServiceName, InfoBarEvent, InfoBarTuner
-
-from Components.Sources.Clock import Clock
-from Components.Sources.ServiceList import ServiceList
-
-from WebComponents.Sources.ServiceListRecursive import ServiceListRecursive
-from WebComponents.Sources.Volume import Volume
-from WebComponents.Sources.EPG import EPG
-from WebComponents.Sources.Timer import Timer
-from WebComponents.Sources.Movie import Movie
-from WebComponents.Sources.Message import Message
-from WebComponents.Sources.PowerState import PowerState
-from WebComponents.Sources.RemoteControl import RemoteControl
-from WebComponents.Sources.Settings import Settings
-from WebComponents.Sources.SubServices import SubServices
-from WebComponents.Sources.ParentControl import ParentControl
-from WebComponents.Sources.About import About
-from WebComponents.Sources.RequestData import RequestData
+#      - better error handling
+#      - use namespace parser
 
-from Components.Sources.FrontendStatus import FrontendStatus
+from Tools.Import import my_import
 
+from Components.Sources.Source import ObsoleteSource
 from Components.Converter.Converter import Converter
-
 from Components.Element import Element
 
 from xml.sax import make_parser
 from xml.sax.handler import ContentHandler, feature_namespaces
-
+from xml.sax.saxutils import escape as escape_xml
 from twisted.python import util
+from urllib2 import quote
 
-import sys
-import time
-# prototype of the new web frontend template system.
+#DO NOT REMOVE THIS IMPORT
+#It IS used (dynamically)
+from WebScreens import *
+#DO NOT REMOVE THIS IMPORT
 
-class WebScreen(Screen):
-       def __init__(self, session, request):
-               Screen.__init__(self, session)
-               self.stand_alone = True
-               self.request = request
-               self.instance = None
-               
-# a test screen
-class TestScreen(InfoBarServiceName, InfoBarEvent,InfoBarTuner, WebScreen):
-       def __init__(self, session,request):
-               WebScreen.__init__(self, session,request)
-               InfoBarServiceName.__init__(self)
-               InfoBarEvent.__init__(self)
-               InfoBarTuner.__init__(self)
-               self["CurrentTime"] = Clock()
-#              self["TVSystem"] = Config(config.av.tvsystem)
-#              self["OSDLanguage"] = Config(config.osd.language)
-#              self["FirstRun"] = Config(config.misc.firstrun)
-               from enigma import eServiceReference
-               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')
-               self["SwitchService"] = ServiceList(fav, command_func = self.zapTo, validate_commands=False)
-               self["ServiceList"] = ServiceList(fav, command_func = self.getServiceList, validate_commands=False)
-               self["ServiceListRecursive"] = ServiceListRecursive(session, func=ServiceListRecursive.FETCH)
-               self["ParentControlList"] = ParentControl(session)
-               self["SubServices"] = SubServices(session)
-               self["Volume"] = Volume(session)
-               self["EPGTITLE"] = EPG(session,func=EPG.TITLE)
-               self["EPGSERVICE"] = EPG(session,func=EPG.SERVICE)
-               self["EPGNOW"] = EPG(session,func=EPG.NOW)
-               self["TimerList"] = Timer(session,func = Timer.LIST)
-               self["TimerAddEventID"] = Timer(session,func = Timer.ADDBYID)
-               self["TimerAdd"] = Timer(session,func = Timer.ADD)
-               self["TimerDel"] = Timer(session,func = Timer.DEL)
-               self["TimerChange"] = Timer(session,func = Timer.CHANGE)
-               self["TimerListWrite"] = Timer(session,func = Timer.WRITE)
-               self["TVBrowser"] = Timer(session,func = Timer.TVBROWSER)
-               self["RecordNow"] = Timer(session,func = Timer.RECNOW)
-               self["MovieList"] = Movie(session,func = Movie.LIST)
-               self["MovieFileDel"] = Movie(session,func = Movie.DEL)
-               self["Volume"] = Volume(session)
-               self["Message"] = Message(session)
-               self["PowerState"] = PowerState(session)
-               self["RemoteControl"] = RemoteControl(session)
-               self["Settings"] = Settings(session)
-               
-               self["About"] = About(session)
-               
-       def getServiceList(self, sRef):
-               self["ServiceList"].root = sRef
-
-       def zapTo(self, reftozap):
-               from Components.config import config
-               pc = config.ParentalControl.configured.value
-               if pc:
-                       config.ParentalControl.configured.value = False
-               self.session.nav.playService(reftozap)
-               if pc:
-                       config.ParentalControl.configured.value = pc
-               """
-               switching config.ParentalControl.configured.value
-               ugly, but necessary :(
-               """
-
-# TODO: (really.) put screens into own files.
-class Streaming(WebScreen):
-       def __init__(self, session,request):
-               WebScreen.__init__(self, session,request)
-               from Components.Sources.StreamService import StreamService
-               self["StreamService"] = StreamService(self.session.nav)
-
-class StreamingM3U(WebScreen):
-       def __init__(self, session,request):
-               WebScreen.__init__(self, session,request)
-               from Components.Sources.StaticText import StaticText
-               from Components.Sources.Config import Config
-               from Components.config import config
-               self["ref"] = StaticText()
-               self["localip"] = RequestData(request,what=RequestData.HOST)
-
-class TsM3U(WebScreen):
-       def __init__(self, session,request):
-               WebScreen.__init__(self, session,request)
-               from Components.Sources.StaticText import StaticText
-               from Components.Sources.Config import Config
-               from Components.config import config
-               self["file"] = StaticText()
-               self["localip"] = RequestData(request,what=RequestData.HOST)
-
-class RestartTwisted(WebScreen):
-       def __init__(self, session,request):
-               WebScreen.__init__(self, session,request)
-               import plugin
-               plugin.restartWebserver()
-               
-class GetPid(WebScreen):
-      def __init__(self, session,request):
-         WebScreen.__init__(self, session,request)
-         from Components.Sources.StaticText import StaticText
-         from enigma import iServiceInformation
-         pids = self.session.nav.getCurrentService()
-         if pids is not None:
-                 pidinfo = pids.info()
-                 VPID = hex(pidinfo.getInfo(iServiceInformation.sVideoPID))
-                 APID = hex(pidinfo.getInfo(iServiceInformation.sAudioPID))
-                 PPID = hex(pidinfo.getInfo(iServiceInformation.sPMTPID))
-         self["pids"] = StaticText("%s,%s,%s"%(PPID.lstrip("0x"),VPID.lstrip("0x"),APID.lstrip("0x")))
-         self["localip"] = RequestData(request,what=RequestData.HOST)
-
-
-# implements the 'render'-call.
-# this will act as a downstream_element, like a renderer.
+
+global screen_cache
+screen_cache = {}
+
+# The classes and Function in File handle all ScreenPage-based requests
+# ScreenPages use enigma2 standard functionality to bring contents to a webfrontend
+#
+# Like Skins a ScreenPage can consist of several Elements and Converters
+
+#===============================================================================
+# OneTimeElement
+#
+# This is the Standard Element for Rendering a "standard" WebElement
+#===============================================================================
 class OneTimeElement(Element):
        def __init__(self, id):
                Element.__init__(self)
                self.source_id = id
 
-       # CHECKME: is this ok performance-wise?
        def handleCommand(self, args):
-               if self.source_id.find(",") >=0:
+               if ',' in self.source_id:
                        paramlist = self.source_id.split(",")
-                       list={}
+                       list = {}
                        for key in paramlist:
-                               arg = args.get(key, [])
-                               if len(arg) == 0:
-                                       list[key] = None        
-                               elif len(arg) == 1:
-                                       list[key] = "".join(arg)        
-                               elif len(arg) == 2:
+                               arg = args.get(key, ())
+                               Len = len(arg)
+                               if Len == 0:
+                                       list[key] = None
+                               elif Len == 1:
+                                       list[key] = "".join(arg)
+                               elif Len == 2:
                                        list[key] = arg[0]
                        self.source.handleCommand(list)
                else:
-                       for c in args.get(self.source_id, []):
+                       for c in args.get(self.source_id, ()):
                                self.source.handleCommand(c)
-               
-       def render(self, stream):
+
+       def render(self, request):
                t = self.source.getHTML(self.source_id)
-               stream.write(t)
+               request.write(t)
 
        def execBegin(self):
-               pass
-       
+               self.suspended = False
+
        def execEnd(self):
-               pass
-       
+               self.suspended = True
+
        def onShow(self):
                pass
 
        def onHide(self):
                pass
-       
+
        def destroy(self):
                pass
 
+#===============================================================================
+# MacroElement
+#
+# A MacroElement helps using OneTimeElements inside a (Simple)ListFiller Loop
+#===============================================================================
+class MacroElement(OneTimeElement):
+       def __init__(self, id, macro_dict, macro_name):
+               OneTimeElement.__init__(self, id)
+               self.macro_dict = macro_dict
+               self.macro_name = macro_name
+
+       def render(self, request):
+               self.macro_dict[self.macro_name] = self.source.getHTML(self.source_id)
+
+#===============================================================================
+# StreamingElement
+#
+# In difference to an OneTimeElement a StreamingElement sends an ongoing Stream
+# of Data. The end of the Streaming is usually when the client disconnects
+#===============================================================================
 class StreamingElement(OneTimeElement):
        def __init__(self, id):
                OneTimeElement.__init__(self, id)
-               self.stream = None
+               self.request = None
 
        def changed(self, what):
-               if self.stream:
-                       self.render(self.stream)
+               if self.request:
+                       self.render(self.request)
 
-       def setStream(self, stream):
-               self.stream = stream
+       def setRequest(self, request):
+               self.request = request
 
+#===============================================================================
+# ListItem
+#
 # a to-be-filled list item
+#===============================================================================
 class ListItem:
        def __init__(self, name, filternum):
                self.name = name
                self.filternum = filternum
-       
+
+#===============================================================================
+# ListMacroItem
+#
+# MacroItem inside a (Simple)ListFiller
+#===============================================================================
+class ListMacroItem:
+       def __init__(self, macrodict, macroname):
+               self.macrodict = macrodict
+               self.macroname = macroname
+
+
+#===============================================================================
+# TextToHTML
+#
+# Returns the String as is
+#===============================================================================
 class TextToHTML(Converter):
        def __init__(self, arg):
                Converter.__init__(self, arg)
 
        def getHTML(self, id):
-               return self.source.text # encode & etc. here!
+               return self.source.text.replace('\xc2\x86', '').replace('\xc2\x87', '').decode("utf-8", "ignore").encode("utf-8") # encode & etc. here!
+
+#===============================================================================
+# TextToXML
+#
+# Escapes the given Text to be XML conform
+#===============================================================================
+class TextToXML(Converter):
+       def __init__(self, arg):
+               Converter.__init__(self, arg)
+
+       def getHTML(self, id):
+               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")
+
+#===============================================================================
+# TextToURL
+#
+# Escapes the given Text so it can be used inside a URL
+#===============================================================================
+class TextToURL(Converter):
+       def __init__(self, arg):
+               Converter.__init__(self, arg)
+
+       def getHTML(self, id):
+               return self.source.text.replace(" ", "%20").replace("+", "%2b").replace("&", "%26").replace('\xc2\x86', '').replace('\xc2\x87', '').decode("utf-8", "ignore").encode("utf-8")
 
+#===============================================================================
+# ReturnEmptyXML
+# 
+# Returns a XML only consisting of <rootElement />
+#===============================================================================
 class ReturnEmptyXML(Converter):
        def __init__(self, arg):
                Converter.__init__(self, arg)
-               
+
        def getHTML(self, id):
-               return "<rootElement></rootElement>"
+               return "<rootElement />"
 
-# a null-output. Useful if you only want to issue a command.
+#===============================================================================
+# Null
+# Return simply NOTHING
+# Useful if you only want to issue a command.
+#===============================================================================
 class Null(Converter):
        def __init__(self, arg):
                Converter.__init__(self, arg)
@@ -241,6 +191,12 @@ class Null(Converter):
        def getHTML(self, id):
                return ""
 
+
+#===============================================================================
+# JavascriptUpdate
+#
+# Transforms a string into a javascript update pattern
+#===============================================================================
 class JavascriptUpdate(Converter):
        def __init__(self, arg):
                Converter.__init__(self, arg)
@@ -248,12 +204,75 @@ class JavascriptUpdate(Converter):
        def getHTML(self, id):
                # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
                #                all other will replace this in JS
-               return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '&deg;'))
+               return '<script>parent.set("%s", "%s");</script>\n' % (id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '&deg;'))
+
+#===============================================================================
+# SimpleListFiller
+#
+# The performant 'one-dimensonial listfiller' engine (podlfe)
+#===============================================================================
+class SimpleListFiller(Converter):
+       def __init__(self, arg):
+               Converter.__init__(self, arg)
+               
+       def getText(self):
+               l = self.source.simplelist
+               conv_args = self.converter_arguments            
+               
+               list = [ ]
+               for element in conv_args:
+                       if isinstance(element, basestring):
+                               list.append((element, None))
+                       elif isinstance(element, ListItem):
+                               list.append((element, element.filternum))
+                       elif isinstance(element, ListMacroItem):
+                               list.append(element.macrodict[element.macroname], None)
+                       else:
+                               raise Exception("neither string, ListItem nor ListMacroItem")
+                       
+               strlist = [ ]
+               append = strlist.append
+               for item in l:
+                       if item is None:
+                               item = ""
+                               
+                       for (element, filternum) in list:
+                               #filter out "non-displayable" Characters - at the very end, do it the hard way...
+                               item = str(item).replace('\xc2\x86', '').replace('\xc2\x87', '').replace("\x19", "").replace("\x1c", "").replace("\x1e", "").decode("utf-8", "ignore").encode("utf-8")
+                               
+                               if not filternum:
+                                       append(element)
+                               elif filternum == 2:
+                                       append(item.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
+                               elif filternum == 3:                                    
+                                       append(escape_xml(item))
+                               elif filternum == 4:
+                                       append(item.replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
+                               elif filternum == 5:
+                                       append(quote(item))
+                               elif filternum == 6:
+                                       time = parseint(item) or 0
+                                       t = localtime(time)
+                                       append("%02d:%02d" % (t.tm_hour, t.tm_min))
+                               elif filternum == 7:
+                                       time = parseint(item) or 0
+                                       t = localtime(time)
+                                       append("%d min" % (time / 60))
+                               else:
+                                       append(item)
+               # (this will be done in c++ later!)
 
+               return ''.join(strlist)         
+       
+       text = property(getText)
+                       
+#===============================================================================
 # the performant 'listfiller'-engine (plfe)
+#===============================================================================
 class ListFiller(Converter):
        def __init__(self, arg):
                Converter.__init__(self, arg)
+#              print "ListFiller-arg: ",arg
 
        def getText(self):
                l = self.source.list
@@ -262,82 +281,181 @@ class ListFiller(Converter):
 
                # now build a ["string", 1, "string", 2]-styled list, with indices into the
                # list to avoid lookup of item name for each entry
-               lutlist = [ isinstance(element, basestring) and (element, None) or (lut[element.name], element.filternum) for element in conv_args ]
+               lutlist = [ ]
+               for element in conv_args:
+                       if isinstance(element, basestring):
+                               lutlist.append((element, None))
+                       elif isinstance(element, ListItem):
+                               lutlist.append((lut[element.name], element.filternum))
+                       elif isinstance(element, ListMacroItem):
+                               lutlist.append((element.macrodict[element.macroname], None))
+                       else:
+                               raise Exception("neither string, ListItem nor ListMacroItem")
 
                # now, for the huge list, do:
                strlist = [ ]
                append = strlist.append
                for item in l:
-                       for (element, filternum) in lutlist:
+                       for (element, filternum) in lutlist:                    
+                               #None becomes ""
+                               curitem = ""
+                               if filternum:
+                                       #filter out "non-displayable" Characters - at the very end, do it the hard way...
+                                       curitem = str(item[element]).replace('\xc2\x86', '').replace('\xc2\x87', '').replace("\x19", "").replace("\x1c", "").replace("\x1e", "").decode("utf-8", "ignore").encode("utf-8")
+                                       if curitem is None:
+                                               curitem = ""
+                               else:
+                                       if element is None:
+                                               element = ""
+                                               
                                if not filternum:
                                        append(element)
                                elif filternum == 2:
-                                       append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
+                                       append(curitem.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
                                elif filternum == 3:
-                                       append(str(item[element]).replace("&", "&amp;").replace("<", "&lt;").replace('"', '&quot;').replace(">", "&gt;"))
+                                       append( escape_xml( curitem ))
                                elif filternum == 4:
-                                       append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
+                                       append(curitem.replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
+                               elif filternum == 5:
+                                       append(quote(curitem))
+                               elif filternum == 6:
+                                       from time import localtime
+                                       time = int(float(curitem)) or 0
+                                       t = localtime(time)
+                                       append("%02d:%02d" % (t.tm_hour, t.tm_min))
+                               elif filternum == 7:
+                                       from time import localtime
+                                       time = int(float(curitem)) or 0
+                                       t = localtime(time)
+                                       append("%d min" % (time / 60))                                  
                                else:
-                                       append(str(item[element]))
+                                       append(curitem)
                # (this will be done in c++ later!)
+
                return ''.join(strlist)
 
        text = property(getText)
 
+#===============================================================================
+# webifHandler
+#
+# Handles the Content of a Web-Request
+# It looks up the source, instantiates the Element and Calls the Converter
+#===============================================================================
 class webifHandler(ContentHandler):
-       def __init__(self, session,request):
+       def __init__(self, session, request):
                self.res = [ ]
                self.mode = 0
                self.screen = None
                self.session = session
                self.screens = [ ]
                self.request = request
-       
+               self.macros = { }
+
+       def start_element(self, attrs):
+               scr = self.screen
+
+               wsource = attrs["source"]
+
+               path = wsource.split('.')
+               while len(path) > 1:
+                       scr = self.screen.getRelatedScreen(path[0])
+                       if scr is None:
+                               print "[webif.py] Parent Screen not found!"
+                               print wsource
+                       path = path[1:]
+
+               source = scr.get(path[0])
+
+               if isinstance(source, ObsoleteSource):
+                       # however, if we found an "obsolete source", issue warning, and resolve the real source.
+                       print "WARNING: WEBIF '%s' USES OBSOLETE SOURCE '%s', USE '%s' INSTEAD!" % (name, wsource, source.new_source)
+                       print "OBSOLETE SOURCE WILL BE REMOVED %s, PLEASE UPDATE!" % (source.removal_date)
+                       if source.description:
+                               print source.description
+
+                       wsource = source.new_source
+               else:
+                       pass
+                       # otherwise, use that source.
+
+               self.source = source
+               self.source_id = str(attrs.get("id", wsource))
+               self.is_streaming = "streaming" in attrs
+               self.macro_name = attrs.get("macro") or None
+
+       def end_element(self):
+               # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
+               if not self.is_streaming:
+                       if self.macro_name is None:
+                               c = OneTimeElement(self.source_id)
+                       else:
+                               c = MacroElement(self.source_id, self.macros, self.macro_name)
+               else:
+                       assert self.macro_name is None
+                       c = StreamingElement(self.source_id)
+
+               c.connect(self.source)
+               self.res.append(c)
+               self.screen.renderer.append(c)
+               del self.source
+
+       def start_convert(self, attrs):
+               ctype = attrs["type"]
+
+               # TODO: we need something better here
+               if ctype[:4] == "web:": # for now
+                       self.converter = eval(ctype[4:])
+               else:
+                       try:
+                               self.converter = my_import('.'.join(("Components", "Converter", ctype))).__dict__.get(ctype)
+                       except ImportError:
+                               self.converter = my_import('.'.join(("Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype))).__dict__.get(ctype)
+               self.sub = [ ]
+
+       def end_convert(self):
+               if len(self.sub) == 1:
+                       self.sub = self.sub[0]
+               c = self.converter(self.sub)
+               c.connect(self.source)
+               self.source = c
+               del self.sub
+
+       def parse_item(self, attrs):
+               if "name" in attrs:
+                       filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4, "urlencode": 5, "time": 6, "minutes": 7}[attrs.get("filter", "")]
+                       self.sub.append(ListItem(attrs["name"], filter))
+               else:
+                       assert "macro" in attrs, "e2:item must have a name= or macro= attribute!"
+                       self.sub.append(ListMacroItem(self.macros, attrs["macro"]))
+
        def startElement(self, name, attrs):
                if name == "e2:screen":
-                       self.screen = eval(attrs["name"])(self.session,self.request) # fixme
+                       self.screen = eval(attrs["name"])(self.session, self.request) # fixme
                        self.screens.append(self.screen)
                        return
-       
+
                if name[:3] == "e2:":
                        self.mode += 1
 
-               tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
-               tag.insert(0, name)
-               tag.insert(0, '<')
-               tag.append('>')
-               tag = ''.join(tag)#.encode('utf-8')
+               tag = '<' + name + ''.join([' %s="%s"' % x for x in attrs.items()]) + '>'
+               #tag = tag.encode('utf-8')
 
                if self.mode == 0:
                        self.res.append(tag)
                elif self.mode == 1: # expect "<e2:element>"
                        assert name == "e2:element", "found %s instead of e2:element" % name
-                       source = attrs["source"]
-                       self.source_id = str(attrs.get("id", source))
-                       self.source = self.screen[source]
-                       self.is_streaming = "streaming" in attrs
+                       self.start_element(attrs)
                elif self.mode == 2: # expect "<e2:convert>"
                        if name[:3] == "e2:":
                                assert name == "e2:convert"
-                               
-                               ctype = attrs["type"]
-                               
-                                       # TODO: we need something better here
-                               if ctype[:4] == "web:": # for now
-                                       self.converter = eval(ctype[4:])
-                               else:
-                                       try:
-                                               self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
-                                       except ImportError:
-                                               self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
-                               self.sub = [ ]
+                               self.start_convert(attrs)
                        else:
                                self.sub.append(tag)
                elif self.mode == 3:
                        assert name == "e2:item", "found %s instead of e2:item!" % name
-                       assert "name" in attrs, "e2:item must have a name= attribute!"
-                       filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4}[attrs.get("filter", "")]
-                       self.sub.append(ListItem(attrs["name"], filter))
+
+                       self.parse_item(attrs)
 
        def endElement(self, name):
                if name == "e2:screen":
@@ -350,37 +468,22 @@ class webifHandler(ContentHandler):
                elif self.mode == 2 and name[:3] != "e2:":
                        self.sub.append(tag)
                elif self.mode == 2: # closed 'convert' -> sub
-                       if len(self.sub) == 1:
-                               self.sub = self.sub[0]
-                       c = self.converter(self.sub)
-                       c.connect(self.source)
-                       self.source = c
-                       del self.sub
+                       self.end_convert()
                elif self.mode == 1: # closed 'element'
-                       # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
-                       if not self.is_streaming:
-                               c = OneTimeElement(self.source_id)
-                       else:
-                               c = StreamingElement(self.source_id)
-                       
-                       c.connect(self.source)
-                       self.res.append(c)
-                       self.screen.renderer.append(c)
-                       del self.source
-
+                       self.end_element()
                if name[:3] == "e2:":
                        self.mode -= 1
 
        def processingInstruction(self, target, data):
                self.res.append('<?' + target + ' ' + data + '>')
-       
+
        def characters(self, ch):
                ch = ch.encode('utf-8')
                if self.mode == 0:
                        self.res.append(ch)
                elif self.mode == 2:
                        self.sub.append(ch)
-       
+
        def startEntity(self, name):
                self.res.append('&' + name + ';');
 
@@ -395,59 +498,68 @@ class webifHandler(ContentHandler):
                        screen.doClose()
                self.screens = [ ]
 
-def renderPage(stream, path, req, session):
-       
+#===============================================================================
+# renderPage
+#
+# Creates the Handler for a Request and calls it
+# Also ensures that the Handler is finished after the Request is done
+#===============================================================================
+def renderPage(request, path, session):
        # read in the template, create required screens
        # we don't have persistense yet.
        # if we had, this first part would only be done once.
-       handler = webifHandler(session,req)
+       handler = webifHandler(session, request)
        parser = make_parser()
        parser.setFeature(feature_namespaces, 0)
        parser.setContentHandler(handler)
-       print "__file__: ",__file__
-       print "path: ",path
        parser.parse(open(util.sibpath(__file__, path)))
-       
+
        # by default, we have non-streaming pages
        finish = True
-       
+
        # first, apply "commands" (aka. URL argument)
        for x in handler.res:
                if isinstance(x, Element):
-                       x.handleCommand(req.args)
+                       x.handleCommand(request.args)
 
        handler.execBegin()
 
        # now, we have a list with static texts mixed
        # with non-static Elements.
-       # flatten this list, write into the stream.
+       # flatten this list, write into the request.
        for x in handler.res:
                if isinstance(x, Element):
                        if isinstance(x, StreamingElement):
                                finish = False
-                               x.setStream(stream)
-                       x.render(stream)
+                               x.setRequest(request)
+                       x.render(request)
                else:
-                       stream.write(str(x))
+                       request.write(str(x))
 
-       def ping(s):
-               from twisted.internet import reactor
-               s.write("\n");
-               reactor.callLater(3, ping, s)
-       
        # if we met a "StreamingElement", there is at least one
        # element which wants to output data more than once,
        # i.e. on host-originated changes.
        # in this case, don't finish yet, don't cleanup yet,
        # but instead do that when the client disconnects.
        if finish:
-               handler.cleanup()
-               stream.finish()
-       else:
-               # ok.
-               # you *need* something which constantly sends something in a regular interval,
-               # in order to detect disconnected clients.
-               # i agree that this "ping" sucks terrible, so better be sure to have something 
-               # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
-               ping(stream)
-               stream.closed_callback = lambda: handler.cleanup()
+               requestFinish(handler, request)
+       
+       else:   
+               def requestFinishDeferred(nothing, handler, request):
+                       from twisted.internet import reactor
+                       reactor.callLater(0, requestFinish, handler, request)                           
+               
+               d = request.notifyFinish()
+
+               d.addBoth( requestFinishDeferred, handler, request )
+                                                       
+#===============================================================================
+# requestFinish
+#
+# This has to be/is called at the end of every ScreenPage-based Request
+#===============================================================================
+def requestFinish(handler, request):
+       handler.cleanup()
+       request.finish()        
+       
+       del handler