mirror of git://git.psyced.org/git/pypsyc
159 lines
4.3 KiB
Python
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())
|