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