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