pypsyc/mjacob2/pypsyc/core/mmp.py

159 lines
4.3 KiB
Python

"""
pypsyc.core.mmp
~~~~~~~~~~~~~~~
Classes which are related to the routing layer (called MMP).
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
from cStringIO import StringIO
from twisted.internet.tcp import BaseClient
from twisted.protocols.basic import LineReceiver
class Uni(str):
"""
This class provides some helpers for working with PSYC Uniforms.
It can be initialized with a string or a list (:meth:`from_parts`).
"""
def __init__(self, uni):
str.__init__(uni)
self.prefix = uni.split(':', 1)[0]
def into_parts(self):
tmp = self.split(':', 1)[1]
return tmp.strip('/').split('/')
@classmethod
def from_parts(cls, parts, prefix='psyc'):
"""
Construct a new UNI object from a list of parts. A prefix other than
'psyc' can be given optionally.
:param parts: the parts which represent the UNI's path
:type parts: list of strings
:param prefix: prefix of the UNI
:type prefix: string
"""
uni = str.__new__(cls, '%s://%s' % (prefix, '/'.join(parts)))
uni.prefix = prefix
return uni
def chain(self, child):
return Uni('/'.join((self.strip('/'), child)))
def is_descendant_of(self, other):
"""Return whether this uni is a descendant of another."""
other = other.strip('/')
l = len(other)
return self[0:l] == other and self[l] == '/'
def is_ancestor_of(self, other):
"""Return whether this uni is an ancestor of another."""
self_ = self.strip('/')
l = len(self_)
return other[0:l] == self_ and other[l] == '/'
class Header(dict):
"""
A class which provides simple access to the header of a PSYC Packet.
.. attribute:: source
'_source' variable of the packet header as :class:`Uni`
.. attribute:: target
'_target' variable of the packet header as :class:`Uni`
.. attribute:: context
'_context' variable of the packet header as :class:`Uni`
"""
def _init(self):
"""Initialize the header with the set values."""
source = self.get('_source')
if source: self.source = Uni(source)
else: self.source = None
target = self.get('_target')
if target: self.target = Uni(target)
else: self.target = None
context = self.get('_context')
if context: self.context = Uni(context)
else: self.context = None
class Circuit(LineReceiver, object):
"""Base class for all PSYC (MMP) Circuits."""
delimiter = '\n'
def __init__(self):
# states:
# 0 = uninitialized
# 1 = header
# 2 = content
self.state = 0
self.vars = {'=': {}}
self.inited = None
def connectionMade(self):
if not hasattr(self, 'initiator'):
self.initiator = isinstance(self.transport, BaseClient)
if self.initiator:
self._init()
def _init(self):
self.send({}, '')
if callable(self.inited):
self.inited()
del self.inited
del self.initiator
def lineReceived(self, line):
if line == '|': # end of packet
if self.state != 0: # the connection is inited already
header = Header(self.vars['='])
header.update(self.vars[':'])
header._init()
self.packet_received(header, self.content)
elif not getattr(self, 'initiator', True):
self._init()
# reset parser state
self.state = 1
self.vars[':'] = {}
self.content = []
elif self.state == 1:
if line == '':
self.state = 2
else:
self.vars[line[0]].__setitem__(*line[1:].split('\t', 1))
elif self.state == 2:
self.content.append(line)
def packet_received(self, header, content):
"""Overide this in subclass."""
raise NotImplementedError
def send(self, header, content):
"""Send a packet over this circuit."""
out = StringIO()
out.writelines(':%s\t%s\n' % x for x in header.iteritems())
content = '\n'.join(content)
if content:
out.write('\n')
out.write(content)
out.write('\n')
out.write('|\n')
self.transport.write(out.getvalue())