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