Ejemplo n.º 1
0
 def test_pack_unpack_presentationStatus(self):
     STATUSES=[
         ( ["okay"], "okay" ),
         ( ["transitioning"], "transitioning" ),
         ( ["fault"], "fault" ),
         ( ["other"], "other" ),
         ( ["okay", "sub"], "okay sub" ),
         ( ["transitioning", "sub1", "sub2"], "transitioning sub1 sub2" ),
     ]
     for (VALUE, ENCODED) in STATUSES:
         c=CII(presentationStatus=VALUE)
         self.assertEquals(c.presentationStatus, VALUE)
         
         msg=c.pack()
         j=json.loads(msg)
         self.assertEquals(j["presentationStatus"], ENCODED)
         self.assertEquals(len(j.keys()), 1)
         
         d=CII.unpack(msg)
         self.assertEquals(OMIT, d.protocolVersion)
         self.assertEquals(OMIT, d.contentId)
         self.assertEquals(OMIT, d.contentIdStatus)
         self.assertEquals(VALUE, d.presentationStatus)
         self.assertEquals(OMIT, d.mrsUrl)
         self.assertEquals(OMIT, d.wcUrl)
         self.assertEquals(OMIT, d.tsUrl)
         self.assertEquals(OMIT, d.teUrl)
         self.assertEquals(OMIT, d.timelines)
         self.assertEquals(OMIT, d.private)
Ejemplo n.º 2
0
 def onClientConnect(self, webSock):
     """Force not sending, if blocking"""
     if not self._blocking:
         super(BlockableCIIServer,self).onClientConnect(webSock)
     else:
         self.getConnections()[webSock]["prevCII"] = CII()
     self.onNumClientsChange(len(self.getConnections()))
Ejemplo n.º 3
0
 def _onCII(self, newCII):
     self.latestCII = newCII
     self.onCiiReceived(newCII)
     
     # take a diff since we cannot assume the received message is a diff
     diff=CII.diff(self.cii, newCII)
     changes=diff.definedProperties()
     
     if len(changes) > 0:      
         self.log.debug("Changed properties: "+ " ".join(changes))
         self.cii.update(diff)
         
         # now we examine changes and fire change specific callbacks as well as a general callback 
         for name in changes:
             if name in changes:
                 funcname = self._callBackFuncNames[name]
                 callback = getattr(self, funcname)
                 if callback is not None:
                     newValue=getattr(diff, name)
                     callback(newValue)
         
         
         # fire general catch-all callback
         self.onChange(changes)
     else:
         self.log.debug("No properties have changed")
Ejemplo n.º 4
0
Archivo: cii.py Proyecto: bbc/pydvbcss
    def _onCII(self, newCII):
        self.latestCII = newCII
        self.onCiiReceived(newCII)

        # take a diff since we cannot assume the received message is a diff
        diff = CII.diff(self.cii, newCII)
        changes = diff.definedProperties()

        if len(changes) > 0:
            self.log.debug("Changed properties: " + " ".join(changes))
            self.cii.update(diff)

            # now we examine changes and fire change specific callbacks as well as a general callback
            for name in changes:
                if name in changes:
                    funcname = self._callBackFuncNames[name]
                    callback = getattr(self, funcname)
                    if callback is not None:
                        newValue = getattr(diff, name)
                        callback(newValue)

            # fire general catch-all callback
            self.onChange(changes)
        else:
            self.log.debug("No properties have changed")
Ejemplo n.º 5
0
 def _ws_on_message(self, msg):
     self.log.debug("Message received.")
     if not msg.is_text:
         self._ws_on_error("Protocol error - message received was not a text frame")
         return
     try:
         cii = CII.unpack(msg.data)
     except Exception,e:
         self._ws_on_error("Protocol error - message received could not be parsed as a CII message: "+str(msg)+". Continuing anyway. Cause was: "+str(e)+"\n")
         return
Ejemplo n.º 6
0
 def test_pack_unpack_protocolVersion(self):
     c=CII(protocolVersion="1.1")
     self.assertEquals(c.protocolVersion, "1.1")
     
     msg=c.pack()
     j=json.loads(msg)
     self.assertEquals(j["protocolVersion"], "1.1")
     self.assertEquals(len(j.keys()), 1)
     
     d=CII.unpack(msg)
     self.assertEquals("1.1", c.protocolVersion)
     self.assertEquals(OMIT, d.contentId)
     self.assertEquals(OMIT, d.contentIdStatus)
     self.assertEquals(OMIT, d.presentationStatus)
     self.assertEquals(OMIT, d.mrsUrl)
     self.assertEquals(OMIT, d.wcUrl)
     self.assertEquals(OMIT, d.tsUrl)
     self.assertEquals(OMIT, d.teUrl)
     self.assertEquals(OMIT, d.timelines)
     self.assertEquals(OMIT, d.private)
Ejemplo n.º 7
0
 def test_create_empty(self):
     c=CII()
     self.assertEquals(OMIT, c.protocolVersion)
     self.assertEquals(OMIT, c.contentId)
     self.assertEquals(OMIT, c.contentIdStatus)
     self.assertEquals(OMIT, c.presentationStatus)
     self.assertEquals(OMIT, c.mrsUrl)
     self.assertEquals(OMIT, c.wcUrl)
     self.assertEquals(OMIT, c.tsUrl)
     self.assertEquals(OMIT, c.teUrl)
     self.assertEquals(OMIT, c.timelines)
     self.assertEquals(OMIT, c.private)
Ejemplo n.º 8
0
 def test_pack_unpack_teUrl(self):
     VALUE="ws://1.2.3.4:5678/seilgr"
     c=CII(teUrl=VALUE)
     self.assertEquals(c.teUrl, VALUE)
     
     msg=c.pack()
     j=json.loads(msg)
     self.assertEquals(j["teUrl"], VALUE)
     self.assertEquals(len(j.keys()), 1)
     
     d=CII.unpack(msg)
     self.assertEquals(OMIT, d.protocolVersion)
     self.assertEquals(OMIT, d.contentId)
     self.assertEquals(OMIT, d.contentIdStatus)
     self.assertEquals(OMIT, d.presentationStatus)
     self.assertEquals(OMIT, d.mrsUrl)
     self.assertEquals(OMIT, d.wcUrl)
     self.assertEquals(OMIT, d.tsUrl)
     self.assertEquals(VALUE, d.teUrl)
     self.assertEquals(OMIT, d.timelines)
     self.assertEquals(OMIT, d.private)
Ejemplo n.º 9
0
 def test_pack_unpack_mrsUrl(self):
     VALUE="http://blah.com"
     c=CII(mrsUrl=VALUE)
     self.assertEquals(c.mrsUrl, VALUE)
     
     msg=c.pack()
     j=json.loads(msg)
     self.assertEquals(j["mrsUrl"], VALUE)
     self.assertEquals(len(j.keys()), 1)
     
     d=CII.unpack(msg)
     self.assertEquals(OMIT, d.protocolVersion)
     self.assertEquals(OMIT, d.contentId)
     self.assertEquals(OMIT, d.contentIdStatus)
     self.assertEquals(OMIT, d.presentationStatus)
     self.assertEquals(VALUE, d.mrsUrl)
     self.assertEquals(OMIT, d.wcUrl)
     self.assertEquals(OMIT, d.tsUrl)
     self.assertEquals(OMIT, d.teUrl)
     self.assertEquals(OMIT, d.timelines)
     self.assertEquals(OMIT, d.private)
Ejemplo n.º 10
0
 def test_pack_unpack_contentIdStatus(self):
     for VALUE in ["partial","final"]:
         c=CII(contentIdStatus=VALUE)
         self.assertEquals(c.contentIdStatus, VALUE)
         
         msg=c.pack()
         j=json.loads(msg)
         self.assertEquals(j["contentIdStatus"], VALUE)
         self.assertEquals(len(j.keys()), 1)
         
         d=CII.unpack(msg)
         self.assertEquals(OMIT, d.protocolVersion)
         self.assertEquals(OMIT, d.contentId)
         self.assertEquals(VALUE, d.contentIdStatus)
         self.assertEquals(OMIT, d.presentationStatus)
         self.assertEquals(OMIT, d.mrsUrl)
         self.assertEquals(OMIT, d.wcUrl)
         self.assertEquals(OMIT, d.tsUrl)
         self.assertEquals(OMIT, d.teUrl)
         self.assertEquals(OMIT, d.timelines)
         self.assertEquals(OMIT, d.private)
Ejemplo n.º 11
0
 def test_pack_unpack_contentId(self):
     VALUE="dvb://a.b.c.d"
     c=CII(contentId=VALUE)
     self.assertEquals(c.contentId, VALUE)
     
     msg=c.pack()
     j=json.loads(msg)
     self.assertEquals(j["contentId"], VALUE)
     self.assertEquals(len(j.keys()), 1)
     
     d=CII.unpack(msg)
     self.assertEquals(OMIT, d.protocolVersion)
     self.assertEquals(VALUE, d.contentId)
     self.assertEquals(OMIT, d.contentIdStatus)
     self.assertEquals(OMIT, d.presentationStatus)
     self.assertEquals(OMIT, d.mrsUrl)
     self.assertEquals(OMIT, d.wcUrl)
     self.assertEquals(OMIT, d.tsUrl)
     self.assertEquals(OMIT, d.teUrl)
     self.assertEquals(OMIT, d.timelines)
     self.assertEquals(OMIT, d.private)
Ejemplo n.º 12
0
 def _onClientMessage(self, webSock, message):
     msg = json.loads(str(message))
     print msg
     print
     
     if "cii" in msg:
         cii = CII.unpack(json.dumps(msg["cii"]))
     else:
         cii = CII()
         
     controlTimestamps = {}
     if "controlTimestamps" in msg:
         for timelineSelector, recvControlTimestamp in msg["controlTimestamps"].items():
             ct = ControlTimestamp.unpack(json.dumps(recvControlTimestamp))
             controlTimestamps[timelineSelector] = ct
         
     options = {}
     if "options" in msg:
         options = msg["options"]
         
     self.onUpdate(cii,controlTimestamps, options)
Ejemplo n.º 13
0
 def test_unpack_ignore_unknown_fields(self):
     msg="""{ "flurble" : 5 }"""
     c=CII.unpack(msg)
     self.assertEquals(OMIT, c.protocolVersion)
     self.assertEquals(OMIT, c.contentId)
     self.assertEquals(OMIT, c.contentIdStatus)
     self.assertEquals(OMIT, c.presentationStatus)
     self.assertEquals(OMIT, c.mrsUrl)
     self.assertEquals(OMIT, c.wcUrl)
     self.assertEquals(OMIT, c.tsUrl)
     self.assertEquals(OMIT, c.teUrl)
     self.assertEquals(OMIT, c.timelines)
     self.assertEquals(OMIT, c.private)
Ejemplo n.º 14
0
 def test_unpack_empty_message(self):
     msg="{}"
     c=CII.unpack(msg)
     self.assertEquals(OMIT, c.protocolVersion)
     self.assertEquals(OMIT, c.contentId)
     self.assertEquals(OMIT, c.contentIdStatus)
     self.assertEquals(OMIT, c.presentationStatus)
     self.assertEquals(OMIT, c.mrsUrl)
     self.assertEquals(OMIT, c.wcUrl)
     self.assertEquals(OMIT, c.tsUrl)
     self.assertEquals(OMIT, c.teUrl)
     self.assertEquals(OMIT, c.timelines)
     self.assertEquals(OMIT, c.private)
Ejemplo n.º 15
0
    def __init__(self, ciiUrl):
        """\
        **Initialisation takes the following parameters:**
        
        :param ciiUrl: (:class:`str`) The WebSocket URL of the CSS-CII Server (e.g. "ws://127.0.0.1/myservice/cii")
        """
        super(CIIClient,self).__init__()
        self.log = logging.getLogger("dvbcss.protocol.client.cii.CIIClient")
        self._conn = CIIClientConnection(ciiUrl)
        self._conn.onCII = self._onCII
        self._conn.onConnected = self._onConnectionOpen
        self._conn.onDisconnected = self._onConnectionClose
        self._conn.onProtocolError = self._onProtocolError
        
        self.connected = False #: True if currently connected to the server, otherwise False.

        self.cii = CII()       #: (:class:`~dvbcss.protocol.cii.CII`) CII object representing the CII state at the server    
        self.latestCII = None  #: (:class:`~dvbcss.protocol.cii.CII` or :class:`None`) The most recent CII message received from the server or None if nothing has yet been received. 
                
        self._callBackFuncNames = {}
        for name in CII.allProperties():
            funcname = "on" + name[0].upper() + name[1:] + "Change"
            self._callBackFuncNames[name] = funcname
Ejemplo n.º 16
0
Archivo: cii.py Proyecto: bbc/pydvbcss
 def _ws_on_message(self, msg):
     self.log.debug("Message received.")
     if not msg.is_text:
         self._ws_on_error(
             "Protocol error - message received was not a text frame")
         return
     try:
         cii = CII.unpack(msg.data)
     except Exception, e:
         self._ws_on_error(
             "Protocol error - message received could not be parsed as a CII message: "
             + str(msg) + ". Continuing anyway. Cause was: " + str(e) +
             "\n")
         return
Ejemplo n.º 17
0
Archivo: cii.py Proyecto: bbc/pydvbcss
    def __init__(self, ciiUrl):
        """\
        **Initialisation takes the following parameters:**
        
        :param ciiUrl: (:class:`str`) The WebSocket URL of the CSS-CII Server (e.g. "ws://127.0.0.1/myservice/cii")
        """
        super(CIIClient, self).__init__()
        self.log = logging.getLogger("dvbcss.protocol.client.cii.CIIClient")
        self._conn = CIIClientConnection(ciiUrl)
        self._conn.onCII = self._onCII
        self._conn.onConnected = self._onConnectionOpen
        self._conn.onDisconnected = self._onConnectionClose
        self._conn.onProtocolError = self._onProtocolError

        self.connected = False  #: True if currently connected to the server, otherwise False.

        self.cii = CII(
        )  #: (:class:`~dvbcss.protocol.cii.CII`) CII object representing the CII state at the server
        self.latestCII = None  #: (:class:`~dvbcss.protocol.cii.CII` or :class:`None`) The most recent CII message received from the server or None if nothing has yet been received.

        self._callBackFuncNames = {}
        for name in CII.allProperties():
            funcname = "on" + name[0].upper() + name[1:] + "Change"
            self._callBackFuncNames[name] = funcname
Ejemplo n.º 18
0
Archivo: cii.py Proyecto: bbc/pydvbcss
    def updateClients(self, sendOnlyDiff=True, sendIfEmpty=False):
        """\
        Send update of current CII state from the :data:`CIIServer.cii` object to all connected clients.
        
        :param sendOnlyDiff: (bool, default=True) Send only the properties in the CII state that have changed since last time a message was sent. Set to False to send the entire message.
        :param sendIfEmpty: (bool, default=False) Set to True to force that a CII message be sent, even if it will be empty (e.g. no change since last time)
        
        By default this method will only send a CII message to clients informing them of the differencesin state since last time a message was sent to them.
        If no properties have changed at all, then no message will be sent.
        
        The two optional arguments allow you to change this behaviour. For example, to force the messages sent to include all properties, even if they have not changed:
        
        .. code-block:: python
        
            myCiiServer.updateClients(sendOnlyDiff=False)
            
        To additionally force it to send even if the CII state held at this server has no values for any of the properties:

        .. code-block:: python
        
            myCiiServer.updateClients(sendOnlyDiff=False, sendIfEmpty=True)
            
        """
        connections = self.getConnections()
        for webSock in connections:
            self.log.debug("Sending CII to connection " + webSock.id())
            connectionData = connections[webSock]
            prevCII = connectionData["prevCII"]

            # perform rewrite substitutions, if any
            cii = self._customiseCii(webSock)

            # work out whether we are sending the full CII or a diff
            if sendOnlyDiff:
                diff = CII.diff(prevCII, cii)
                toSend = diff
                # enforce requirement that contentId must be accompanied by contentIdStatus
                if diff.contentId != OMIT:
                    toSend.contentIdStatus = cii.contentIdStatus
            else:
                toSend = cii

            # only send if forced to, or if the mesage to send is not empty (all OMITs)
            if sendIfEmpty or toSend.definedProperties():
                webSock.send(toSend.pack())

            connectionData["prevCII"] = cii.copy()
Ejemplo n.º 19
0
    def updateClients(self, sendOnlyDiff=True,sendIfEmpty=False):
        """\
        Send update of current CII state from the :data:`CIIServer.cii` object to all connected clients.
        
        :param sendOnlyDiff: (bool, default=True) Send only the properties in the CII state that have changed since last time a message was sent. Set to False to send the entire message.
        :param sendIfEmpty: (bool, default=False) Set to True to force that a CII message be sent, even if it will be empty (e.g. no change since last time)
        
        By default this method will only send a CII message to clients informing them of the differencesin state since last time a message was sent to them.
        If no properties have changed at all, then no message will be sent.
        
        The two optional arguments allow you to change this behaviour. For example, to force the messages sent to include all properties, even if they have not changed:
        
        .. code-block:: python
        
            myCiiServer.updateClients(sendOnlyDiff=False)
            
        To additionally force it to send even if the CII state held at this server has no values for any of the properties:

        .. code-block:: python
        
            myCiiServer.updateClients(sendOnlyDiff=False, sendIfEmpty=True)
            
        """
        connections = self.getConnections()
        for webSock in connections:
            self.log.debug("Sending CII to connection "+webSock.id())
            connectionData = connections[webSock]
            prevCII = connectionData["prevCII"]
            
            # work out whether we are sending the full CII or a diff
            if sendOnlyDiff:
                diff = CII.diff(prevCII, self.cii)
                toSend = diff
                # enforce requirement that contentId must be accompanied by contentIdStatus
                if diff.contentId != OMIT:
                    toSend.contentIdStatus = self.cii.contentIdStatus
            else:
                toSend = self.cii
            
            # only send if forced to, or if the mesage to send is not empty (all OMITs)
            if sendIfEmpty or toSend.definedProperties():
                webSock.send(toSend.pack())
                
            connectionData["prevCII"] = self.cii.copy()
Ejemplo n.º 20
0
Archivo: cii.py Proyecto: bbc/pydvbcss
    def __init__(self,
                 maxConnectionsAllowed=-1,
                 enabled=True,
                 initialCII=CII(protocolVersion="1.1"),
                 rewriteHostPort=[]):
        """\
        **Initialisation takes the following parameters:**
        
        :param maxConnectionsAllowed: (int, default=-1) Maximum number of concurrent connections to be allowed, or -1 to allow as many connections as resources allow.
        :param enabled: (bool, default=True) Whether the endpoint is initially enabled (True) or disabled (False)
        :param initialCII: (:class:`dvbcss.protocol.cii.CII`, default=CII(protocolVersion="1.1")) Initial value of CII state.
        :param rewriteHostPort: (list) List of CII property names for which the sub-string '{{host}}' '{{port}}' will be replaced with the host and port that the client connected to. 
        """
        super(CIIServer,
              self).__init__(maxConnectionsAllowed=maxConnectionsAllowed,
                             enabled=enabled)

        self.cii = initialCII.copy()
        self._rewriteHostPort = rewriteHostPort[:]
        """\
Ejemplo n.º 21
0
 def __init__(self, enabled=True, initialCII=CII(protocolVersion="1.1")):
     super(MockCiiServer, self).__init__()
     self.cii = initialCII.copy()
     self._connections = {}
     self._enabled = enabled
     self._updateClientsCalled = False
Ejemplo n.º 22
0
Archivo: cii.py Proyecto: bbc/pydvbcss
class CIIServer(WSServerBase):
    """\
    The CIIServer class implements a server for the CSS-CII protocol. It transparently manages
    the connection and disconnection of clients and provides an interface for simply setting the
    CII state and requesting that it be pushed to any connected clients.
    
    Must be used in conjunction with a cherrypy web server:
    
    1. Ensure the ws4py :class:`~ws4py.server.cherrypyserver.WebSocketPlugin` is subscribed, to the cherrypy server. E.g.
    
       .. code-block:: python
    
         WebSocketPlugin(cherrypy.engine).subscribe()
         
    2. Mount the instance onto a particular URL path on a cherrypy web server. Set the config
       properties for the URL it is to be mounted at as follows:
    
       .. code-block:: python
    
         { 'tools.dvb_cii.on'         : True,
           'tools.dvb_cii.handler_cls': myCiiServerInstance.handler }
        
    Update the :data:`cii` property with the CII state information and call the :func:`updateClients` method to propagate state changes to
    any connected clients.

    When the server is "disabled" it will refuse attempts to connect by sending the HTTP status response 403 "Forbidden".

    When the server has reached its connection limit, it will refuse attempts to connect by sending the HTTP status response 503 "Service unavailable".
    
    This object provides properties:

    * :data:`enabled` (read/write) controls whether this server is enabled or not
    * :data:`cii` (read/write) the CII state that is being shared to connected clients
    
    To allow for servers serving multiple network interfaces, or where the IP address of the interface is not easy to determine, CII Server can be 
    asked to automatically substitute the host and port with the one that the client connected to. Specify the list of property names for which this
    shoud happen as an optional `rewritheostPort` argument when intialising the CIIServer, then use `{{host}}` `{{port}}` within those properties.
    """

    connectionIdPrefix = "cii"
    loggingName = "dvb-css.protocol.server.cii.CIIServer"

    getDefaultConnectionData = lambda self: {
        "prevCII": CII()
    }  # default state for a new connection - no CII info transferred to client yet

    def __init__(self,
                 maxConnectionsAllowed=-1,
                 enabled=True,
                 initialCII=CII(protocolVersion="1.1"),
                 rewriteHostPort=[]):
        """\
        **Initialisation takes the following parameters:**
        
        :param maxConnectionsAllowed: (int, default=-1) Maximum number of concurrent connections to be allowed, or -1 to allow as many connections as resources allow.
        :param enabled: (bool, default=True) Whether the endpoint is initially enabled (True) or disabled (False)
        :param initialCII: (:class:`dvbcss.protocol.cii.CII`, default=CII(protocolVersion="1.1")) Initial value of CII state.
        :param rewriteHostPort: (list) List of CII property names for which the sub-string '{{host}}' '{{port}}' will be replaced with the host and port that the client connected to. 
        """
        super(CIIServer,
              self).__init__(maxConnectionsAllowed=maxConnectionsAllowed,
                             enabled=enabled)

        self.cii = initialCII.copy()
        self._rewriteHostPort = rewriteHostPort[:]
        """\
        A :class:`dvbcss.protocol.cii.CII` message object representing current CII state.
        Set the attributes of this object to update that state.
        
        When :func:`updateClients` is called, it is this state that will be sent to connected clients.
        """

    def _customiseCii(self, webSock):
        cii = self.cii.copy()
        host = webSock.local_address[0]
        port = str(webSock.local_address[1])
        for propName in cii.definedProperties():
            if propName in self._rewriteHostPort:
                propVal = getattr(cii, propName).replace("{{host}}",
                                                         host).replace(
                                                             "{{port}}", port)
                setattr(cii, propName, propVal)
        return cii

    def updateClients(self, sendOnlyDiff=True, sendIfEmpty=False):
        """\
        Send update of current CII state from the :data:`CIIServer.cii` object to all connected clients.
        
        :param sendOnlyDiff: (bool, default=True) Send only the properties in the CII state that have changed since last time a message was sent. Set to False to send the entire message.
        :param sendIfEmpty: (bool, default=False) Set to True to force that a CII message be sent, even if it will be empty (e.g. no change since last time)
        
        By default this method will only send a CII message to clients informing them of the differencesin state since last time a message was sent to them.
        If no properties have changed at all, then no message will be sent.
        
        The two optional arguments allow you to change this behaviour. For example, to force the messages sent to include all properties, even if they have not changed:
        
        .. code-block:: python
        
            myCiiServer.updateClients(sendOnlyDiff=False)
            
        To additionally force it to send even if the CII state held at this server has no values for any of the properties:

        .. code-block:: python
        
            myCiiServer.updateClients(sendOnlyDiff=False, sendIfEmpty=True)
            
        """
        connections = self.getConnections()
        for webSock in connections:
            self.log.debug("Sending CII to connection " + webSock.id())
            connectionData = connections[webSock]
            prevCII = connectionData["prevCII"]

            # perform rewrite substitutions, if any
            cii = self._customiseCii(webSock)

            # work out whether we are sending the full CII or a diff
            if sendOnlyDiff:
                diff = CII.diff(prevCII, cii)
                toSend = diff
                # enforce requirement that contentId must be accompanied by contentIdStatus
                if diff.contentId != OMIT:
                    toSend.contentIdStatus = cii.contentIdStatus
            else:
                toSend = cii

            # only send if forced to, or if the mesage to send is not empty (all OMITs)
            if sendIfEmpty or toSend.definedProperties():
                webSock.send(toSend.pack())

            connectionData["prevCII"] = cii.copy()

    def onClientConnect(self, webSock):
        """If you override this method you must call the base class implementation."""
        self.log.info("Sending initial CII message for connection " +
                      webSock.id())
        cii = self._customiseCii(webSock)
        webSock.send(cii.pack())
        self.getConnections()[webSock]["prevCII"] = cii.copy()

    def onClientDisconnect(self, webSock, connectionData):
        """If you override this method you must call the base class implementation."""
        pass

    def onClientMessage(self, webSock, message):
        """If you override this method you must call the base class implementation."""
        self.log.info("Received unexpected message on connection" +
                      webSock.id() + " : " + str(message))
Ejemplo n.º 23
0
 def test_pack_presentationStatus_not_a_string(self):
     c=CII(presentationStatus="okay")
     with self.assertRaises(ValueError):
         c.pack()
Ejemplo n.º 24
0
 def test_pack_empty_message(self):
     c=CII()
     msg=c.pack()
     j=json.loads(msg)
     self.assertEquals(j,{})
Ejemplo n.º 25
0
class CIIClient(object):
    """\
    Manages a CSS-CII protocol connection to a CSS-CII Server and notifies of changes to CII state.

    Use by subclassing and overriding the following methods:

    * :func:`onConnected`
    * :func:`onDisconnected`
    * :func:`onChange`
    * individual `onXXXXChange()` methods named after each CII property
    * :func:`onCiiReceived` (do not use, by preference)

    If you do not wish to subclass, you can instead create an instance of this
    class and replace the methods listed above with your own functions dynamically.

    The :func:`connect` and :func:`disconnect` methods connect and disconnect the connection to the server
    and :func:`getStatusSummary` provides a human readable summary of CII state.

    This object also provides properties you can query:

    * :data:`cii` represents the current state of CII at the server
    * :data:`latestCII` is the most recently CII message received from the server
    * :data:`connected` indicates whether the connection is currently connect

    """
    def __init__(self, ciiUrl):
        """\
        **Initialisation takes the following parameters:**
        
        :param ciiUrl: (:class:`str`) The WebSocket URL of the CSS-CII Server (e.g. "ws://127.0.0.1/myservice/cii")
        """
        super(CIIClient,self).__init__()
        self.log = logging.getLogger("dvbcss.protocol.client.cii.CIIClient")
        self._conn = CIIClientConnection(ciiUrl)
        self._conn.onCII = self._onCII
        self._conn.onConnected = self._onConnectionOpen
        self._conn.onDisconnected = self._onConnectionClose
        self._conn.onProtocolError = self._onProtocolError
        
        self.connected = False #: True if currently connected to the server, otherwise False.

        self.cii = CII()       #: (:class:`~dvbcss.protocol.cii.CII`) CII object representing the CII state at the server    
        self.latestCII = None  #: (:class:`~dvbcss.protocol.cii.CII` or :class:`None`) The most recent CII message received from the server or None if nothing has yet been received. 
                
        self._callBackFuncNames = {}
        for name in CII.allProperties():
            funcname = "on" + name[0].upper() + name[1:] + "Change"
            self._callBackFuncNames[name] = funcname
    
    def onConnected(self):
        """\
        This method is called when the connection is opened.
        
        |stub-method|
        """
        pass
    
    def onDisconnected(self, code, reason=None):
        """\
        This method is called when the connection is closed.
        
        |stub-method|
        
        :param code:   (:class:`int`) The connection closure code to be sent in the WebSocket disconnect frame
        :param reason: (:class:`str` or :class:`None`) The human readable reason for the closure
        """
        pass
    
    def onChange(self, changedPropertyNames):
        """\
        This method is called when a CII message is received from the server that causes one or more of the CII properties to change to a different value.
        
        :param changedPropertyNames: A :class:`list` of :class:`str` names of the properties that have changed. Query the :data:`cii` attribute to find out the new values.
        """
        pass
    
    def onProtocolError(self, msg):
        """\
        This method is called when there has been an error in the use of the CII protocol - e.g. receiving the wrong kind of message.
           
        |stub-method|
        
        :param msg: A :class:`str` description of the problem.
        """
        pass
    
    def onCiiReceived(self, newCii):
        """\
        This method is called when a CII message is received, but before any 'onXXXXChange()' handlers (if any) are called.
        It is called even if the message does not result in a change to CII state held locally.
        
        By preference is recommended to use the 'onXXXXChange()' handlers instead since these will only be called if there
        is an actual change to the value of a property in CII state.
        
        |stub-method|
        
        :param cii: A :class:`~dvbcss.protocol.cii.CII` object representing the received message.
        """
        pass
    
    def connect(self):
        """\
        Start the client by trying to open the connection.
        
        :throws ConnectionError: There was a problem that meant it was not possible to connect.
        """
        self._conn.connect()
        
    def disconnect(self):
        """\
        Disconnect from the server.
        """
        self._conn.disconnect()
        
    def _onConnectionOpen(self):
        self.connected=True
        self.onConnected()
        
    def _onConnectionClose(self, code, reason):
        self.connected=False
        self.onDisconnected()
            
    def _onProtocolError(self, msg):
        self.log.error("There was a protocol error: "+msg+". Continuing anyway.")
        self.onProtocolError(msg)
        
    def _onCII(self, newCII):
        self.latestCII = newCII
        self.onCiiReceived(newCII)
        
        # take a diff since we cannot assume the received message is a diff
        diff=CII.diff(self.cii, newCII)
        changes=diff.definedProperties()
        
        if len(changes) > 0:      
            self.log.debug("Changed properties: "+ " ".join(changes))
            self.cii.update(diff)
            
            # now we examine changes and fire change specific callbacks as well as a general callback 
            for name in changes:
                if name in changes:
                    funcname = self._callBackFuncNames[name]
                    callback = getattr(self, funcname)
                    if callback is not None:
                        newValue=getattr(diff, name)
                        callback(newValue)
            
            
            # fire general catch-all callback
            self.onChange(changes)
        else:
            self.log.debug("No properties have changed")
                        
    def getStatusSummary(self):
        if self.latestCII is None:
            return "Nothing received from TV yet."
        
        return str(self.cii)
Ejemplo n.º 26
0
 def test_pack_unpack_timelines(self):
     TIMELINES=[
         ( [], [] ),
         ( [ TimelineOption("urn:dvb:css:timeline:pts", 1, 1000, 0.2, OMIT) ],
           [ { "timelineSelector" : "urn:dvb:css:timeline:pts",
               "timelineProperties" : {
                   "unitsPerTick" : 1,
                   "unitsPerSecond" : 1000,
                   "accuracy" : 0.2
               }
             }
           ]
         ),
         ( [ TimelineOption("urn:dvb:css:timeline:pts", 1, 1000, 0.2, OMIT),
             TimelineOption("urn:dvb:css:timeline:temi:1:5", 1001, 30000, OMIT, []),
             TimelineOption("urn:dvb:css:timeline:temi:1:6", 1, 25, OMIT, [{'type':'blah','abc':5},{'type':'bbc','pqr':None}]),
             
           ],
           [ { "timelineSelector" : "urn:dvb:css:timeline:pts",
               "timelineProperties" : {
                   "unitsPerTick" : 1,
                   "unitsPerSecond" : 1000,
                   "accuracy" : 0.2
               }
             },
             { "timelineSelector" : "urn:dvb:css:timeline:temi:1:5",
               "timelineProperties" : {
                   "unitsPerTick" : 1001,
                   "unitsPerSecond" : 30000
               },
               "private" : []
             },
             { "timelineSelector" : "urn:dvb:css:timeline:temi:1:6",
               "timelineProperties" : {
                   "unitsPerTick" : 1,
                   "unitsPerSecond" : 25,
               },
               "private" : [{'type':'blah','abc':5},{'type':'bbc','pqr':None}]
             }
           ]
         ),
     ]
     for (VALUE, ENCODED) in TIMELINES:
         c=CII(timelines=VALUE)
         self.assertEquals(c.timelines, VALUE)
         
         msg=c.pack()
         j=json.loads(msg)
         self.assertEquals(j["timelines"], ENCODED)
         self.assertEquals(len(j.keys()), 1)
         
         d=CII.unpack(msg)
         self.assertEquals(OMIT, d.protocolVersion)
         self.assertEquals(OMIT, d.contentId)
         self.assertEquals(OMIT, d.contentIdStatus)
         self.assertEquals(OMIT, d.presentationStatus)
         self.assertEquals(OMIT, d.mrsUrl)
         self.assertEquals(OMIT, d.wcUrl)
         self.assertEquals(OMIT, d.tsUrl)
         self.assertEquals(OMIT, d.teUrl)
         self.assertEquals(VALUE, d.timelines)
         self.assertEquals(OMIT, d.private)
Ejemplo n.º 27
0
                    if callback is not None:
                        newValue=getattr(diff, name)
                        callback(newValue)
            
            
            # fire general catch-all callback
            self.onChange(changes)
        else:
            self.log.debug("No properties have changed")
                        
    def getStatusSummary(self):
        if self.latestCII is None:
            return "Nothing received from TV yet."
        
        return str(self.cii)

# programmatically create the onXXXChange methods for every property in a CII message
for propertyName in CII.allProperties():
    def f(self, newValue):
        pass
    f.__doc__="Called when the "+propertyName+" property of the CII message has been changed by a state update from the CII Server.\n\n" + \
              "|stub-method|\n\n" + \
              ":param newValue: The new value for this property."
    setattr(CIIClient, "on"+propertyName[0].upper() + propertyName[1:]+"Change", f)


__all__ = [
    "CIIClientConnection",
    "CIIClient",
]
Ejemplo n.º 28
0
 def mock_clientConnects(self):
     """Simulate a client connecting, returns a handle representing that client."""
     mockSock = object()
     self._connections[mockSock] = {"prevCII": CII()}
     self.onClientConnect(mockSock)
     return mockSock
Ejemplo n.º 29
0
Archivo: cii.py Proyecto: bbc/pydvbcss
class CIIClient(object):
    """\
    Manages a CSS-CII protocol connection to a CSS-CII Server and notifies of changes to CII state.

    Use by subclassing and overriding the following methods:

    * :func:`onConnected`
    * :func:`onDisconnected`
    * :func:`onChange`
    * individual `onXXXXChange()` methods named after each CII property
    * :func:`onCiiReceived` (do not use, by preference)

    If you do not wish to subclass, you can instead create an instance of this
    class and replace the methods listed above with your own functions dynamically.

    The :func:`connect` and :func:`disconnect` methods connect and disconnect the connection to the server
    and :func:`getStatusSummary` provides a human readable summary of CII state.

    This object also provides properties you can query:

    * :data:`cii` represents the current state of CII at the server
    * :data:`latestCII` is the most recently CII message received from the server
    * :data:`connected` indicates whether the connection is currently connect

    """
    def __init__(self, ciiUrl):
        """\
        **Initialisation takes the following parameters:**
        
        :param ciiUrl: (:class:`str`) The WebSocket URL of the CSS-CII Server (e.g. "ws://127.0.0.1/myservice/cii")
        """
        super(CIIClient, self).__init__()
        self.log = logging.getLogger("dvbcss.protocol.client.cii.CIIClient")
        self._conn = CIIClientConnection(ciiUrl)
        self._conn.onCII = self._onCII
        self._conn.onConnected = self._onConnectionOpen
        self._conn.onDisconnected = self._onConnectionClose
        self._conn.onProtocolError = self._onProtocolError

        self.connected = False  #: True if currently connected to the server, otherwise False.

        self.cii = CII(
        )  #: (:class:`~dvbcss.protocol.cii.CII`) CII object representing the CII state at the server
        self.latestCII = None  #: (:class:`~dvbcss.protocol.cii.CII` or :class:`None`) The most recent CII message received from the server or None if nothing has yet been received.

        self._callBackFuncNames = {}
        for name in CII.allProperties():
            funcname = "on" + name[0].upper() + name[1:] + "Change"
            self._callBackFuncNames[name] = funcname

    def onConnected(self):
        """\
        This method is called when the connection is opened.
        
        |stub-method|
        """
        pass

    def onDisconnected(self, code, reason=None):
        """\
        This method is called when the connection is closed.
        
        |stub-method|
        
        :param code:   (:class:`int`) The connection closure code to be sent in the WebSocket disconnect frame
        :param reason: (:class:`str` or :class:`None`) The human readable reason for the closure
        """
        pass

    def onChange(self, changedPropertyNames):
        """\
        This method is called when a CII message is received from the server that causes one or more of the CII properties to change to a different value.
        
        :param changedPropertyNames: A :class:`list` of :class:`str` names of the properties that have changed. Query the :data:`cii` attribute to find out the new values.
        """
        pass

    def onProtocolError(self, msg):
        """\
        This method is called when there has been an error in the use of the CII protocol - e.g. receiving the wrong kind of message.
           
        |stub-method|
        
        :param msg: A :class:`str` description of the problem.
        """
        pass

    def onCiiReceived(self, newCii):
        """\
        This method is called when a CII message is received, but before any 'onXXXXChange()' handlers (if any) are called.
        It is called even if the message does not result in a change to CII state held locally.
        
        By preference is recommended to use the 'onXXXXChange()' handlers instead since these will only be called if there
        is an actual change to the value of a property in CII state.
        
        |stub-method|
        
        :param cii: A :class:`~dvbcss.protocol.cii.CII` object representing the received message.
        """
        pass

    def connect(self):
        """\
        Start the client by trying to open the connection.
        
        :throws ConnectionError: There was a problem that meant it was not possible to connect.
        """
        self._conn.connect()

    def disconnect(self):
        """\
        Disconnect from the server.
        """
        self._conn.disconnect()

    def _onConnectionOpen(self):
        self.connected = True
        self.onConnected()

    def _onConnectionClose(self, code, reason):
        self.connected = False
        self.onDisconnected()

    def _onProtocolError(self, msg):
        self.log.error("There was a protocol error: " + msg +
                       ". Continuing anyway.")
        self.onProtocolError(msg)

    def _onCII(self, newCII):
        self.latestCII = newCII
        self.onCiiReceived(newCII)

        # take a diff since we cannot assume the received message is a diff
        diff = CII.diff(self.cii, newCII)
        changes = diff.definedProperties()

        if len(changes) > 0:
            self.log.debug("Changed properties: " + " ".join(changes))
            self.cii.update(diff)

            # now we examine changes and fire change specific callbacks as well as a general callback
            for name in changes:
                if name in changes:
                    funcname = self._callBackFuncNames[name]
                    callback = getattr(self, funcname)
                    if callback is not None:
                        newValue = getattr(diff, name)
                        callback(newValue)

            # fire general catch-all callback
            self.onChange(changes)
        else:
            self.log.debug("No properties have changed")

    def getStatusSummary(self):
        if self.latestCII is None:
            return "Nothing received from TV yet."

        return str(self.cii)
Ejemplo n.º 30
0
    if args.quiet:
        logging.disable(logging.CRITICAL)
    else:
        logging.basicConfig(level=args.loglevel[0])

    cii = CIIClient(ciiUrl)

    # logger for outputting messages
    ciiClientLogger = logging.getLogger("CIIClient")

    # attach callbacks to generate notifications
    cii.onConnected = makeCallback("connected")
    cii.onDisconnected = makeCallback("disconnected")
    cii.onError = makeCallback("error")

    for name in CII.allProperties():
        funcname = "on" + name[0].upper() + name[1:] + "Change"
        callback = makePropertyChangeCallback(name)
        setattr(cii, funcname, callback)

    # specific handler for when a CII 'change' notification callback fires
    def onChange(changes):
        ciiClientLogger.info("CII is now: " + str(cii.cii))

    cii.onChange = onChange

    # connect and goto sleep. All callback activity happens in the websocket's own thread
    cii.connect()
    while True:
        time.sleep(1)
Ejemplo n.º 31
0
Archivo: cii.py Proyecto: bbc/pydvbcss
                        newValue = getattr(diff, name)
                        callback(newValue)

            # fire general catch-all callback
            self.onChange(changes)
        else:
            self.log.debug("No properties have changed")

    def getStatusSummary(self):
        if self.latestCII is None:
            return "Nothing received from TV yet."

        return str(self.cii)


# programmatically create the onXXXChange methods for every property in a CII message
for propertyName in CII.allProperties():

    def f(self, newValue):
        pass
    f.__doc__="Called when the "+propertyName+" property of the CII message has been changed by a state update from the CII Server.\n\n" + \
              "|stub-method|\n\n" + \
              ":param newValue: The new value for this property."
    setattr(CIIClient,
            "on" + propertyName[0].upper() + propertyName[1:] + "Change", f)

__all__ = [
    "CIIClientConnection",
    "CIIClient",
]
Ejemplo n.º 32
0
                            "/ts": {
                                'tools.dvb_ts.on': True,
                                'tools.dvb_ts.handler_cls': tsServer.handler
                            }
                        })

    ciiServer.cii = CII(
        protocolVersion="1.1",
        contentId=CONTENT_ID,
        contentIdStatus="final",
        presentationStatus=["okay"],
        mrsUrl=OMIT,
        tsUrl=
        "ws://{{host}}:{{port}}/ts",  # host & port rewriting has been enabled on the CII server
        wcUrl="udp://{{host}}:%d" % args.
        wc_port,  # host & port rewriting has been enabled on the CII server
        teUrl=OMIT,
        timelines=[
            TimelineOption("urn:dvb:css:timeline:pts",
                           unitsPerTick=1,
                           unitsPerSecond=90000),
            TimelineOption("urn:dvb:css:timeline:temi:1:1",
                           unitsPerTick=1,
                           unitsPerSecond=50)
        ])

    ptsTimeline = CorrelatedClock(parentClock=wallClock,
                                  tickRate=90000,
                                  correlation=Correlation(wallClock.ticks, 0))
    temiTimeline = CorrelatedClock(parentClock=ptsTimeline,
                                   tickRate=50,
Ejemplo n.º 33
0
        logging.disable(logging.CRITICAL)
    else:
        logging.basicConfig(level=args.loglevel[0])


    cii = CIIClient(ciiUrl)
    
    # logger for outputting messages
    ciiClientLogger = logging.getLogger("CIIClient")

    # attach callbacks to generate notifications
    cii.onConnected           = makeCallback("connected")
    cii.onDisconnected        = makeCallback("disconnected")
    cii.onError               = makeCallback("error");

    for name in CII.allProperties():
        funcname="on" + name[0].upper() + name[1:] + "Change"
        callback = makePropertyChangeCallback(name)
        setattr(cii, funcname, callback)

    # specific handler for when a CII 'change' notification callback fires
    def onChange(changes):
        ciiClientLogger.info("CII is now: "+str(cii.cii))
        
    cii.onChange = onChange

    # connect and goto sleep. All callback activity happens in the websocket's own thread    
    cii.connect()
    while True:
        time.sleep(1)