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