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