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