Add location and tags to the timer dialog in the web interface
[vuplus_dvbapp-plugin] / webinterface / src / webif.py
1 # -*- coding: UTF-8 -*-
2 Version = '$Header$';
3
4 # OK, this is more than a proof of concept
5 # things to improve:
6 #  - nicer code
7 #  - screens need to be defined somehow else.
8 #       I don't know how, yet. Probably each in an own file.
9 #  - more components, like the channellist
10 #  - better error handling
11 #  - use namespace parser
12 from enigma import eServiceReference
13
14 from Screens.Screen import Screen
15 from Screens.ChannelSelection import service_types_tv, service_types_radio
16 from Tools.Import import my_import
17
18 from Components.Sources.Source import ObsoleteSource
19
20 from Components.Sources.Clock import Clock
21 from Components.Sources.ServiceList import ServiceList
22
23 from WebComponents.Sources.ServiceListRecursive import ServiceListRecursive
24 from WebComponents.Sources.Volume import Volume
25 from WebComponents.Sources.EPG import EPG
26 from WebComponents.Sources.Timer import Timer
27 from WebComponents.Sources.Movie import Movie
28 from WebComponents.Sources.Message import Message
29 from WebComponents.Sources.PowerState import PowerState
30 from WebComponents.Sources.RemoteControl import RemoteControl
31 from WebComponents.Sources.Settings import Settings
32 from WebComponents.Sources.SubServices import SubServices
33 from WebComponents.Sources.ParentControl import ParentControl
34 from WebComponents.Sources.About import About
35 from WebComponents.Sources.RequestData import RequestData
36 from WebComponents.Sources.AudioTracks import AudioTracks
37 from WebComponents.Sources.WAPfunctions import WAPfunctions
38 from WebComponents.Sources.MP import MP
39 from WebComponents.Sources.ServiceListReload import ServiceListReload
40 from WebComponents.Sources.AT import AT
41 from WebComponents.Sources.CurrentService import CurrentService
42 from WebComponents.Sources.LocationsAndTags import LocationsAndTags
43
44 from Components.Sources.FrontendStatus import FrontendStatus
45
46 from Components.Converter.Converter import Converter
47
48 from Components.Element import Element
49
50 from xml.sax import make_parser
51 from xml.sax.handler import ContentHandler, feature_namespaces
52 from xml.sax.saxutils import escape as escape_xml
53 from twisted.python import util
54 from urllib2 import quote
55
56 # prototype of the new web frontend template system.
57
58 class WebScreen(Screen):
59         def __init__(self, session, request):
60                 Screen.__init__(self, session)
61                 self.stand_alone = True
62                 self.request = request
63                 self.instance = None
64
65 class DummyWebScreen(WebScreen):
66         #use it, if you dont need any source, just to can do a static file with an xml-file
67         def __init__(self, session, request):
68                 WebScreen.__init__(self, session, request)
69
70 class UpdateWebScreen(WebScreen):
71         def __init__(self, session, request):
72                 WebScreen.__init__(self, session, request)
73                 self["CurrentTime"] = Clock()
74
75 class MessageWebScreen(WebScreen):
76         def __init__(self, session, request):
77                 WebScreen.__init__(self, session, request)
78                 self["Message"] = Message(session,func = Message.PRINT)
79                 self["GetAnswer"] = Message(session,func = Message.ANSWER)
80
81 class ServiceListReloadWebScreen(WebScreen):
82         def __init__(self, session, request):
83                 WebScreen.__init__(self, session, request)
84                 self["ServiceListReload"] = ServiceListReload(session)
85
86 class AudioWebScreen(WebScreen):
87         def __init__(self, session, request):
88                 WebScreen.__init__(self, session, request)
89                 self["AudioTracks"] = AudioTracks(session, func=AudioTracks.GET)
90                 self["SelectAudioTrack"] = AudioTracks(session, func=AudioTracks.SET)
91
92 class AboutWebScreen(WebScreen):
93         def __init__(self, session, request):
94                 WebScreen.__init__(self, session, request)
95                 self["About"] = About(session)
96
97 class VolumeWebScreen(WebScreen):
98         def __init__(self, session, request):
99                 WebScreen.__init__(self, session, request)
100                 self["Volume"] = Volume(session)
101
102 class SettingsWebScreen(WebScreen):
103         def __init__(self, session, request):
104                 WebScreen.__init__(self, session, request)
105                 self["Settings"] = Settings(session)
106
107 class SubServiceWebScreen(WebScreen):
108         def __init__(self, session, request):
109                 WebScreen.__init__(self, session, request)
110                 self["SubServices"] = SubServices(session)
111
112 class ServiceWebScreen(WebScreen):
113         def __init__(self, session, request):
114                 WebScreen.__init__(self, session, request)
115
116                 fav = eServiceReference(service_types_tv + ' FROM BOUQUET "bouquets.tv" ORDER BY bouquet')
117                 self["SwitchService"] = ServiceList(fav, command_func = self.zapTo, validate_commands=False)
118                 self["ServiceList"] = ServiceList(fav, command_func = self.getServiceList, validate_commands=False)
119                 self["ServiceListRecursive"] = ServiceListRecursive(session, func=ServiceListRecursive.FETCH)
120                 self["localip"] = RequestData(request,what=RequestData.HOST)
121
122         def getServiceList(self, sRef):
123                 self["ServiceList"].root = sRef
124
125         def zapTo(self, reftozap):
126                 from Components.config import config
127                 pc = config.ParentalControl.configured.value
128                 if pc:
129                         config.ParentalControl.configured.value = False
130                 if config.plugins.Webinterface.allowzapping.value:
131                         self.session.nav.playService(reftozap)
132                 if pc:
133                         config.ParentalControl.configured.value = pc
134                 """
135                 switching config.ParentalControl.configured.value
136                 ugly, but necessary :(
137                 """
138
139 class LocationsAndTagsWebScreen(WebScreen):
140         def __init__(self, session, request):
141                 WebScreen.__init__(self, session, request)
142                 self["Locations"] = LocationsAndTags(session,LocationsAndTags.LOCATIONS)
143                 self["Tags"] = LocationsAndTags(session,LocationsAndTags.TAGS)
144
145 class EPGWebScreen(WebScreen):
146         def __init__(self, session, request):
147                 WebScreen.__init__(self, session, request)
148                 
149                 self["EPGTITLE"] = EPG(session,func=EPG.TITLE)
150                 self["EPGSERVICE"] = EPG(session,func=EPG.SERVICE)
151                 self["EPGNOW"] = EPG(session,func=EPG.NOW)
152                 self["EPGNEXT"] = EPG(session,func=EPG.NEXT)
153                 self["EPGBOUQUET"] = EPG(session,func=EPG.BOUQUET)
154                 
155         def getServiceList(self, sRef):
156                 self["ServiceList"].root = sRef
157
158 class MovieWebScreen(WebScreen):
159         def __init__(self, session, request):
160                 WebScreen.__init__(self, session, request)
161                 from Components.MovieList import MovieList
162                 from Tools.Directories import resolveFilename,SCOPE_HDD
163                 movielist = MovieList(eServiceReference("2:0:1:0:0:0:0:0:0:0:" + resolveFilename(SCOPE_HDD)))
164                 self["MovieList"] = Movie(session,movielist,func = Movie.LIST)
165                 self["MovieFileDel"] = Movie(session,movielist,func = Movie.DEL)
166                 self["MovieTags"] = Movie(session,movielist,func = Movie.TAGS)
167                 self["localip"] = RequestData(request,what=RequestData.HOST)
168
169 class MediaPlayerWebScreen(WebScreen):
170         def __init__(self, session, request):
171                 WebScreen.__init__(self, session, request)
172                 self["FileList"] = MP(session,func = MP.LIST)
173                 self["PlayFile"] = MP(session,func = MP.PLAY)
174                 self["Command"] = MP(session,func = MP.COMMAND)
175                 self["WritePlaylist"] = MP(session,func = MP.WRITEPLAYLIST)
176
177 class AutoTimerWebScreen(WebScreen):
178         def __init__(self, session, request):
179                 WebScreen.__init__(self, session, request)
180                 self["AutoTimerList"] = AT(session,func = AT.LIST)
181                 self["AutoTimerWrite"] = AT(session,func = AT.WRITE)
182
183 class TimerWebScreen(WebScreen):
184         def __init__(self, session, request):
185                 WebScreen.__init__(self, session, request)
186                 self["TimerList"] = Timer(session,func = Timer.LIST)
187                 self["TimerAddEventID"] = Timer(session,func = Timer.ADDBYID)
188                 self["TimerAdd"] = Timer(session,func = Timer.ADD)
189                 self["TimerDel"] = Timer(session,func = Timer.DEL)
190                 self["TimerChange"] = Timer(session,func = Timer.CHANGE)
191                 self["TimerListWrite"] = Timer(session,func = Timer.WRITE)
192                 self["TVBrowser"] = Timer(session,func = Timer.TVBROWSER)
193                 self["RecordNow"] = Timer(session,func = Timer.RECNOW)
194
195 class RemoteWebScreen(WebScreen):
196         def __init__(self, session, request):
197                 WebScreen.__init__(self, session, request)
198                 self["RemoteControl"] = RemoteControl(session)
199
200 class PowerWebScreen(WebScreen):
201         def __init__(self, session, request):
202                 WebScreen.__init__(self, session, request)
203                 self["PowerState"] = PowerState(session)
204
205 class ParentControlWebScreen(WebScreen):
206         def __init__(self, session, request):
207                 WebScreen.__init__(self, session, request)
208                 self["ParentControlList"] = ParentControl(session)
209
210 class WAPWebScreen(WebScreen):
211         def __init__(self, session, request):
212                 WebScreen.__init__(self, session, request)
213                 self["WAPFillOptionListYear"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
214                 self["WAPFillOptionListDay"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
215                 self["WAPFillOptionListMonth"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
216                 self["WAPFillOptionListShour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
217                 self["WAPFillOptionListSmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
218                 self["WAPFillOptionListEhour"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
219                 self["WAPFillOptionListEmin"] = WAPfunctions(session,func = WAPfunctions.LISTTIME)
220
221                 self["WAPFillOptionListRecord"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
222                 self["WAPFillOptionListAfterEvent"] = WAPfunctions(session,func = WAPfunctions.OPTIONLIST)
223
224                 self["WAPFillValueName"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
225                 self["WAPFillValueDescr"] = WAPfunctions(session,func = WAPfunctions.FILLVALUE)
226                 self["WAPFillLocation"] = WAPfunctions(session,func = WAPfunctions.LOCATIONLIST)
227                 self["WAPFillTags"] = WAPfunctions(session,func = WAPfunctions.TAGLIST)
228
229                 self["WAPFillOptionListRepeated"] = WAPfunctions(session,func = WAPfunctions.REPEATED)
230                 self["WAPServiceList"] = WAPfunctions(session, func = WAPfunctions.SERVICELIST)
231
232                 self["WAPdeleteOldOnSave"] = WAPfunctions(session,func = WAPfunctions.DELETEOLD)
233
234 class StreamingWebScreen(WebScreen):
235         def __init__(self, session, request):
236                 WebScreen.__init__(self, session, request)
237                 from Components.Sources.StreamService import StreamService
238                 self["StreamService"] = StreamService(self.session.nav)
239
240 class M3UStreamingWebScreen(WebScreen):
241         def __init__(self, session, request):
242                 WebScreen.__init__(self, session, request)
243                 from Components.Sources.StaticText import StaticText
244                 from Components.Sources.Config import Config
245                 from Components.config import config
246                 self["ref"] = StaticText()
247                 self["localip"] = RequestData(request,what=RequestData.HOST)
248
249 class M3UStreamingCurrentServiceWebScreen(WebScreen):
250         def __init__(self, session, request):
251                 WebScreen.__init__(self, session, request)
252                 self["CurrentService"] = CurrentService(session)
253                 self["localip"] = RequestData(request,what=RequestData.HOST)
254
255 class TsM3U(WebScreen):
256         def __init__(self, session, request):
257                 WebScreen.__init__(self, session, request)
258                 from Components.Sources.StaticText import StaticText
259                 from Components.Sources.Config import Config
260                 from Components.config import config
261                 self["file"] = StaticText()
262                 self["localip"] = RequestData(request,what=RequestData.HOST)
263
264 class RestartWebScreen(WebScreen):
265         def __init__(self, session, request):
266                 WebScreen.__init__(self, session, request)
267                 import plugin
268                 plugin.restartWebserver(session)
269
270 class GetPid(WebScreen):
271           def __init__(self, session, request):
272                  WebScreen.__init__(self, session, request)
273                  from Components.Sources.StaticText import StaticText
274                  from enigma import iServiceInformation
275                  pids = self.session.nav.getCurrentService()
276                  if pids is not None:
277                         pidinfo = pids.info()
278                         VPID = hex(pidinfo.getInfo(iServiceInformation.sVideoPID))
279                         APID = hex(pidinfo.getInfo(iServiceInformation.sAudioPID))
280                         PPID = hex(pidinfo.getInfo(iServiceInformation.sPMTPID))
281                         self["pids"] = StaticText("%s,%s,%s"%(PPID.lstrip("0x"),VPID.lstrip("0x"),APID.lstrip("0x")))
282                  else:
283                         self["pids"] = StaticText("0x,0x,0x")
284
285                  self["localip"] = RequestData(request,what=RequestData.HOST)
286
287
288 # implements the 'render'-call.
289 # this will act as a downstream_element, like a renderer.
290 class OneTimeElement(Element):
291         def __init__(self, id):
292                 Element.__init__(self)
293                 self.source_id = id
294
295         # CHECKME: is this ok performance-wise?
296         def handleCommand(self, args):
297                 if self.source_id.find(",") >=0:
298                         paramlist = self.source_id.split(",")
299                         list={}
300                         for key in paramlist:
301                                 arg = args.get(key, [])
302                                 if len(arg) == 0:
303                                         list[key] = None
304                                 elif len(arg) == 1:
305                                         list[key] = "".join(arg)
306                                 elif len(arg) == 2:
307                                         list[key] = arg[0]
308                         self.source.handleCommand(list)
309                 else:
310                         for c in args.get(self.source_id, []):
311                                 self.source.handleCommand(c)
312
313         def render(self, stream):
314                 t = self.source.getHTML(self.source_id)
315                 stream.write(t)
316
317         def execBegin(self):
318                 self.suspended = False
319
320         def execEnd(self):
321                 self.suspended = True
322
323         def onShow(self):
324                 pass
325
326         def onHide(self):
327                 pass
328
329         def destroy(self):
330                 pass
331
332 class MacroElement(OneTimeElement):
333         def __init__(self, id, macro_dict, macro_name):
334                 OneTimeElement.__init__(self, id)
335                 self.macro_dict = macro_dict
336                 self.macro_name = macro_name
337
338         def render(self, stream):
339                 self.macro_dict[self.macro_name] = self.source.getHTML(self.source_id)
340
341 class StreamingElement(OneTimeElement):
342         def __init__(self, id):
343                 OneTimeElement.__init__(self, id)
344                 self.stream = None
345
346         def changed(self, what):
347                 if self.stream:
348                         self.render(self.stream)
349
350         def setStream(self, stream):
351                 self.stream = stream
352
353 # a to-be-filled list item
354 class ListItem:
355         def __init__(self, name, filternum):
356                 self.name = name
357                 self.filternum = filternum
358
359 class ListMacroItem:
360         def __init__(self, macrodict, macroname):
361                 self.macrodict = macrodict
362                 self.macroname = macroname
363
364 class TextToHTML(Converter):
365         def __init__(self, arg):
366                 Converter.__init__(self, arg)
367
368         def getHTML(self, id):
369                 return self.source.text # encode & etc. here!
370
371 class TextToXML(Converter):
372         def __init__(self, arg):
373                 Converter.__init__(self, arg)
374
375         def getHTML(self, id):
376                 return escape_xml(self.source.text).replace("\x19", "").replace("\x1c", "").replace("\x1e", "")
377
378 class TextToURL(Converter):
379         def __init__(self, arg):
380                 Converter.__init__(self, arg)
381
382         def getHTML(self, id):
383                 return self.source.text.replace(" ","%20")
384
385 class ReturnEmptyXML(Converter):
386         def __init__(self, arg):
387                 Converter.__init__(self, arg)
388
389         def getHTML(self, id):
390                 return "<rootElement></rootElement>"
391
392 # a null-output. Useful if you only want to issue a command.
393 class Null(Converter):
394         def __init__(self, arg):
395                 Converter.__init__(self, arg)
396
397         def getHTML(self, id):
398                 return ""
399
400 class JavascriptUpdate(Converter):
401         def __init__(self, arg):
402                 Converter.__init__(self, arg)
403
404         def getHTML(self, id):
405                 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
406                 #                all other will replace this in JS
407                 return '<script>parent.set("%s", "%s");</script>\n'%(id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '&deg;'))
408
409 # the performant 'listfiller'-engine (plfe)
410 class ListFiller(Converter):
411         def __init__(self, arg):
412                 Converter.__init__(self, arg)
413 #               print "ListFiller-arg: ",arg
414
415         def getText(self):
416                 l = self.source.list
417                 lut = self.source.lut
418                 conv_args = self.converter_arguments
419
420                 # now build a ["string", 1, "string", 2]-styled list, with indices into the
421                 # list to avoid lookup of item name for each entry
422                 lutlist = [ ]
423                 for element in conv_args:
424                         if isinstance(element, basestring):
425                                 lutlist.append((element, None))
426                         elif isinstance(element, ListItem):
427                                 lutlist.append((lut[element.name], element.filternum))
428                         elif isinstance(element, ListMacroItem):
429                                 lutlist.append((element.macrodict[element.macroname], None))
430                         else:
431                                 raise Exception("neither string, ListItem nor ListMacroItem")
432
433                 # now, for the huge list, do:
434                 strlist = [ ]
435                 append = strlist.append
436                 for item in l:
437                         for (element, filternum) in lutlist:
438                                 if not filternum:
439                                         append(element)
440                                 elif filternum == 2:
441                                         append(str(item[element]).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
442                                 elif filternum == 3:
443                                         append(escape_xml(str(item[element])))
444                                 elif filternum == 4:
445                                         append(str(item[element]).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
446                                 elif filternum == 5:
447                                         append(quote(str(item[element])))
448                                 else:
449                                         append(str(item[element]))
450                 # (this will be done in c++ later!)
451
452                 return ''.join(strlist)
453
454         text = property(getText)
455
456 class webifHandler(ContentHandler):
457         def __init__(self, session, request):
458                 self.res = [ ]
459                 self.mode = 0
460                 self.screen = None
461                 self.session = session
462                 self.screens = [ ]
463                 self.request = request
464                 self.macros = { }
465
466         def start_element(self, attrs):
467                 scr = self.screen
468
469                 wsource = attrs["source"]
470
471                 path = wsource.split('.')
472                 while len(path) > 1:
473                         scr = self.screen.getRelatedScreen(path[0])
474                         if scr is None:
475                                 print "[webif.py] Parent Screen not found!"
476                                 print wsource
477                         path = path[1:]
478
479                 source = scr.get(path[0])
480
481                 if isinstance(source, ObsoleteSource):
482                         # however, if we found an "obsolete source", issue warning, and resolve the real source.
483                         print "WARNING: WEBIF '%s' USES OBSOLETE SOURCE '%s', USE '%s' INSTEAD!" % (name, wsource, source.new_source)
484                         print "OBSOLETE SOURCE WILL BE REMOVED %s, PLEASE UPDATE!" % (source.removal_date)
485                         if source.description:
486                                 print source.description
487
488                         wsource = source.new_source
489                 else:
490                         pass
491                         # otherwise, use that source.
492
493                 self.source = source
494                 self.source_id = str(attrs.get("id", wsource))
495                 self.is_streaming = "streaming" in attrs
496                 self.macro_name = attrs.get("macro") or None
497
498         def end_element(self):
499                 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
500                 if not self.is_streaming:
501                         if self.macro_name is None:
502                                 c = OneTimeElement(self.source_id)
503                         else:
504                                 c = MacroElement(self.source_id, self.macros, self.macro_name)
505                 else:
506                         assert self.macro_name is None
507                         c = StreamingElement(self.source_id)
508
509                 c.connect(self.source)
510                 self.res.append(c)
511                 self.screen.renderer.append(c)
512                 del self.source
513
514         def start_convert(self, attrs):
515                 ctype = attrs["type"]
516
517                         # TODO: we need something better here
518                 if ctype[:4] == "web:": # for now
519                         self.converter = eval(ctype[4:])
520                 else:
521                         try:
522                                 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
523                         except ImportError:
524                                 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
525                 self.sub = [ ]
526
527         def end_convert(self):
528                 if len(self.sub) == 1:
529                         self.sub = self.sub[0]
530                 c = self.converter(self.sub)
531                 c.connect(self.source)
532                 self.source = c
533                 del self.sub
534
535         def parse_item(self, attrs):
536                 if "name" in attrs:
537                         filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4, "urlencode": 5}[attrs.get("filter", "")]
538                         self.sub.append(ListItem(attrs["name"], filter))
539                 else:
540                         assert "macro" in attrs, "e2:item must have a name= or macro= attribute!"
541                         self.sub.append(ListMacroItem(self.macros, attrs["macro"]))
542
543         def startElement(self, name, attrs):
544                 if name == "e2:screen":
545                         self.screen = eval(attrs["name"])(self.session,self.request) # fixme
546                         self.screens.append(self.screen)
547                         return
548
549                 if name[:3] == "e2:":
550                         self.mode += 1
551
552                 tag = [' %s="%s"' %(key,val) for (key, val) in attrs.items()]
553                 tag.insert(0, name)
554                 tag.insert(0, '<')
555                 tag.append('>')
556                 tag = ''.join(tag)#.encode('utf-8')
557
558                 if self.mode == 0:
559                         self.res.append(tag)
560                 elif self.mode == 1: # expect "<e2:element>"
561                         assert name == "e2:element", "found %s instead of e2:element" % name
562                         self.start_element(attrs)
563                 elif self.mode == 2: # expect "<e2:convert>"
564                         if name[:3] == "e2:":
565                                 assert name == "e2:convert"
566                                 self.start_convert(attrs)
567                         else:
568                                 self.sub.append(tag)
569                 elif self.mode == 3:
570                         assert name == "e2:item", "found %s instead of e2:item!" % name
571
572                         self.parse_item(attrs)
573
574         def endElement(self, name):
575                 if name == "e2:screen":
576                         self.screen = None
577                         return
578
579                 tag = "</" + name + ">"
580                 if self.mode == 0:
581                         self.res.append(tag)
582                 elif self.mode == 2 and name[:3] != "e2:":
583                         self.sub.append(tag)
584                 elif self.mode == 2: # closed 'convert' -> sub
585                         self.end_convert()
586                 elif self.mode == 1: # closed 'element'
587                         self.end_element()
588                 if name[:3] == "e2:":
589                         self.mode -= 1
590
591         def processingInstruction(self, target, data):
592                 self.res.append('<?' + target + ' ' + data + '>')
593
594         def characters(self, ch):
595                 ch = ch.encode('utf-8')
596                 if self.mode == 0:
597                         self.res.append(ch)
598                 elif self.mode == 2:
599                         self.sub.append(ch)
600
601         def startEntity(self, name):
602                 self.res.append('&' + name + ';');
603
604         def execBegin(self):
605                 for screen in self.screens:
606                         screen.execBegin()
607
608         def cleanup(self):
609                 print "screen cleanup!"
610                 for screen in self.screens:
611                         screen.execEnd()
612                         screen.doClose()
613                 self.screens = [ ]
614
615 def renderPage(stream, path, req, session):
616
617         # read in the template, create required screens
618         # we don't have persistense yet.
619         # if we had, this first part would only be done once.
620         handler = webifHandler(session,req)
621         parser = make_parser()
622         parser.setFeature(feature_namespaces, 0)
623         parser.setContentHandler(handler)
624         parser.parse(open(util.sibpath(__file__, path)))
625
626         # by default, we have non-streaming pages
627         finish = True
628
629         # first, apply "commands" (aka. URL argument)
630         for x in handler.res:
631                 if isinstance(x, Element):
632                         x.handleCommand(req.args)
633
634         handler.execBegin()
635
636         # now, we have a list with static texts mixed
637         # with non-static Elements.
638         # flatten this list, write into the stream.
639         for x in handler.res:
640                 if isinstance(x, Element):
641                         if isinstance(x, StreamingElement):
642                                 finish = False
643                                 x.setStream(stream)
644                         x.render(stream)
645                 else:
646                         stream.write(str(x))
647
648         def ping(s):
649                 from twisted.internet import reactor
650                 s.write("\n");
651                 reactor.callLater(3, ping, s)
652
653         # if we met a "StreamingElement", there is at least one
654         # element which wants to output data more than once,
655         # i.e. on host-originated changes.
656         # in this case, don't finish yet, don't cleanup yet,
657         # but instead do that when the client disconnects.
658         if finish:
659                 streamFinish(handler, stream)
660         else:
661                 # ok.
662                 # you *need* something which constantly sends something in a regular interval,
663                 # in order to detect disconnected clients.
664                 # i agree that this "ping" sucks terrible, so better be sure to have something
665                 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
666                 ping(stream)
667                 stream.closed_callback = lambda : streamFinish(handler, stream)
668
669 def streamFinish(handler, stream):
670         handler.cleanup()
671         stream.finish()
672         del handler
673         del stream