#
# OK, this is more than a proof of concept
# things to improve:
# - nicer code
# - screens need to be defined somehow else.
# I don't know how, yet. Probably each in an own file.
# - more components, like the channellist
# - better error handling
# - use namespace parser
from Screens.Screen import Screen
from Tools.Import import my_import
# for our testscreen
from Screens.InfoBarGenerics import InfoBarServiceName, InfoBarEvent
from Components.Sources.Clock import Clock
#from Components.Sources.Config import Config
from Components.Sources.ServiceList import ServiceList
from Components.Converter.Converter import Converter
#from Components.config import config
from Components.Element import Element
from xml.sax import make_parser
from xml.sax.handler import ContentHandler, feature_namespaces
from twisted.python import util
import sys
import time
# prototype of the new web frontend template system.
class WebScreen(Screen):
def __init__(self, session):
Screen.__init__(self, session)
self.stand_alone = True
# a test screen
class TestScreen(InfoBarServiceName, InfoBarEvent, WebScreen):
def __init__(self, session):
WebScreen.__init__(self, session)
InfoBarServiceName.__init__(self)
InfoBarEvent.__init__(self)
self["CurrentTime"] = Clock()
# self["TVSystem"] = Config(config.av.tvsystem)
# self["OSDLanguage"] = Config(config.osd.language)
# self["FirstRun"] = Config(config.misc.firstrun)
from enigma import eServiceReference
fav = eServiceReference('1:7:1:0:0:0:0:0:0:0:(type == 1) || (type == 17) || (type == 195) || (type == 25) FROM BOUQUET "userbouquet.favourites.tv" ORDER BY bouquet')
self["ServiceList"] = ServiceList(fav, command_func = self.zapTo)
self["ServiceListBrowse"] = ServiceList(fav, command_func = self.browseTo)
def browseTo(self, reftobrowse):
self["ServiceListBrowse"].root = reftobrowse
def zapTo(self, reftozap):
self.session.nav.playService(reftozap)
class Streaming(WebScreen):
def __init__(self, session):
WebScreen.__init__(self, session)
from Components.Sources.StreamService import StreamService
self["StreamService"] = StreamService(self.session.nav)
# implements the 'render'-call.
# this will act as a downstream_element, like a renderer.
class OneTimeElement(Element):
def __init__(self, id):
Element.__init__(self)
self.source_id = id
# CHECKME: is this ok performance-wise?
def handleCommand(self, args):
for c in args.get(self.source_id, []):
self.source.handleCommand(c)
def render(self, stream):
t = self.source.getHTML(self.source_id)
if isinstance(t, unicode):
t = t.encode("utf-8")
stream.write(t)
def execBegin(self):
pass
def execEnd(self):
pass
def onShow(self):
pass
def onHide(self):
pass
def destroy(self):
pass
class StreamingElement(OneTimeElement):
def __init__(self, id):
OneTimeElement.__init__(self, id)
self.stream = None
def changed(self, what):
if self.stream:
self.render(self.stream)
def setStream(self, stream):
self.stream = stream
# a to-be-filled list item
class ListItem:
def __init__(self, name):
self.name = name
class TextToHTML(Converter):
def __init__(self, arg):
Converter.__init__(self, arg)
def getHTML(self, id):
return self.source.text # encode & etc. here!
# a null-output. Useful if you only want to issue a command.
class Null(Converter):
def __init__(self, arg):
Converter.__init__(self, arg)
def getHTML(self, id):
return ""
def escape(s):
return s.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"')
class JavascriptUpdate(Converter):
def __init__(self, arg):
Converter.__init__(self, arg)
def getHTML(self, id):
return '\n'
# the performant 'listfiller'-engine (plfe)
class ListFiller(Converter):
def __init__(self, arg):
Converter.__init__(self, arg)
def getText(self):
l = self.source.list
lut = self.source.lut
# now build a ["string", 1, "string", 2]-styled list, with indices into the
# list to avoid lookup of item name for each entry
lutlist = []
for element in self.converter_arguments:
if isinstance(element, str):
lutlist.append(element)
elif isinstance(element, ListItem):
lutlist.append(lut[element.name])
# now, for the huge list, do:
res = ""
for item in l:
for element in lutlist:
if isinstance(element, str):
res += element
else:
res += str(item[element])
# (this will be done in c++ later!)
return res
text = property(getText)
class webifHandler(ContentHandler):
def __init__(self, session):
self.res = [ ]
self.mode = 0
self.screen = None
self.session = session
self.screens = [ ]
def startElement(self, name, attrs):
if name == "e2:screen":
self.screen = eval(attrs["name"])(self.session) # fixme
self.screens.append(self.screen)
return
if name[:3] == "e2:":
self.mode += 1
tag = "<" + name + ''.join([' ' + key + '="' + val + '"' for (key, val) in attrs.items()]) + ">"
tag = tag.encode("UTF-8")
if self.mode == 0:
self.res.append(tag)
elif self.mode == 1: # expect ""
assert name == "e2:element", "found %s instead of e2:element" % name
source = attrs["source"]
self.source_id = str(attrs.get("id", source))
self.source = self.screen[source]
self.is_streaming = "streaming" in attrs
elif self.mode == 2: # expect ""
if name[:3] == "e2:":
assert name == "e2:convert"
ctype = attrs["type"]
if ctype[:4] == "web:": # for now
self.converter = eval(ctype[4:])
else:
self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
self.sub = [ ]
else:
self.sub.append(tag)
elif self.mode == 3:
assert name == "e2:item", "found %s instead of e2:item!" % name
assert "name" in attrs, "e2:item must have a name= attribute!"
self.sub.append(ListItem(attrs["name"]))
def endElement(self, name):
if name == "e2:screen":
self.screen = None
return
tag = "" + name + ">"
if self.mode == 0:
self.res.append(tag)
elif self.mode == 2 and name[:3] != "e2:":
self.sub.append(tag)
elif self.mode == 2: # closed 'convert' -> sub
self.sub = lreduce(self.sub)
if len(self.sub) == 1:
self.sub = self.sub[0]
c = self.converter(self.sub)
c.connect(self.source)
self.source = c
del self.sub
elif self.mode == 1: # closed 'element'
# instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
if not self.is_streaming:
c = OneTimeElement(self.source_id)
else:
c = StreamingElement(self.source_id)
c.connect(self.source)
self.res.append(c)
self.screen.renderer.append(c)
del self.source
if name[:3] == "e2:":
self.mode -= 1
def processingInstruction(self, target, data):
self.res.append('' + target + ' ' + data + '>')
def characters(self, ch):
ch = ch.encode("UTF-8")
if self.mode == 0:
self.res.append(ch)
elif self.mode == 2:
self.sub.append(ch)
def startEntity(self, name):
self.res.append('&' + name + ';');
def execBegin(self):
for screen in self.screens:
screen.execBegin()
def cleanup(self):
print "screen cleanup!"
for screen in self.screens:
screen.execEnd()
screen.doClose()
self.screens = [ ]
def lreduce(list):
# ouch, can be made better
res = [ ]
string = None
for x in list:
if isinstance(x, str) or isinstance(x, unicode):
if isinstance(x, unicode):
x = x.encode("UTF-8")
if string is None:
string = x
else:
string += x
else:
if string is not None:
res.append(string)
string = None
res.append(x)
if string is not None:
res.append(string)
string = None
return res
def renderPage(stream, path, req, session):
# read in the template, create required screens
# we don't have persistense yet.
# if we had, this first part would only be done once.
handler = webifHandler(session)
parser = make_parser()
parser.setFeature(feature_namespaces, 0)
parser.setContentHandler(handler)
parser.parse(open(util.sibpath(__file__, path)))
# by default, we have non-streaming pages
finish = True
# first, apply "commands" (aka. URL argument)
for x in handler.res:
if isinstance(x, Element):
x.handleCommand(req.args)
handler.execBegin()
# now, we have a list with static texts mixed
# with non-static Elements.
# flatten this list, write into the stream.
for x in lreduce(handler.res):
if isinstance(x, Element):
if isinstance(x, StreamingElement):
finish = False
x.setStream(stream)
x.render(stream)
else:
stream.write(str(x))
def ping(s):
from twisted.internet import reactor
s.write("\n");
reactor.callLater(3, ping, s)
# if we met a "StreamingElement", there is at least one
# element which wants to output data more than once,
# i.e. on host-originated changes.
# in this case, don't finish yet, don't cleanup yet,
# but instead do that when the client disconnects.
if finish:
handler.cleanup()
stream.finish()
else:
# ok.
# you *need* something which constantly sends something in a regular interval,
# in order to detect disconnected clients.
# i agree that this "ping" sucks terrible, so better be sure to have something
# similar. A "CurrentTime" is fine. Or anything that creates *some* output.
ping(stream)
stream.closed_callback = lambda: handler.cleanup()