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