Source code for astm.protocol

# -*- coding: utf-8 -*-
#
# Copyright (C) 2012 Alexander Shorin
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution.
#

import logging
from threading import Timer, RLock
from collections import namedtuple
from .asynclib import AsyncChat
from .records import HeaderRecord, TerminatorRecord
from .constants import STX, CRLF, ENQ, ACK, NAK, EOT, ENCODING

log = logging.getLogger(__name__)

#: ASTM protocol states set.
STATE = namedtuple('ASTMState', ['init', 'opened', 'transfer'])(*range(3))

__all__ = ['STATE', 'ASTMProtocol']

Timer = type(Timer(None, None))

[docs]class ASTMProtocol(AsyncChat): """Common ASTM protocol routines.""" #: ASTM header record class. astm_header = HeaderRecord #: ASTM terminator record class. astm_terminator = TerminatorRecord #: Flag about chunked transfer. is_chunked_transfer = None #: Operation timeout value. timeout = None encoding = ENCODING strip_terminator = False _last_recv_data = None _last_sent_data = None _state = None _lock = RLock() _timer = None _timer_cls = Timer def __init__(self, sock=None, map=None, timeout=None): super(ASTMProtocol, self).__init__(sock, map) if timeout is not None: self.timeout = timeout def found_terminator(self): while self.inbox: data = self.inbox.popleft() if not data: continue self.dispatch(data)
[docs] def dispatch(self, data): """Dispatcher of received data.""" self._last_recv_data = data if data == ENQ: handler = self.on_enq elif data == ACK: handler = self.on_ack elif data == NAK: handler = self.on_nak elif data == EOT: handler = self.on_eot elif data.startswith(STX): # this looks like a message handler = self.on_message else: handler = lambda: self.default_handler(data) with self._lock: resp = handler() self.start_timer() if resp is not None: self.push(resp)
def default_handler(self, data): raise ValueError('Unable to dispatch data: %r', data) def push(self, data): self._last_sent_data = data return super(ASTMProtocol, self).push(data) def start_timer(self): if self.timeout is None: return self.stop_timer() self._timer = self._timer_cls(self.timeout, self.on_timeout) self._timer.daemon = True self._timer.start() log.debug('Timer %r started', self._timer) def stop_timer(self): if self.timeout is None or self._timer is None: return if self._timer is not None and self._timer.is_alive(): self._timer.cancel() log.debug('Timer %r stopped', self._timer) self._timer = None
[docs] def on_enq(self): """Calls on <ENQ> message receiving."""
[docs] def on_ack(self): """Calls on <ACK> message receiving."""
[docs] def on_nak(self): """Calls on <NAK> message receiving."""
[docs] def on_eot(self): """Calls on <EOT> message receiving."""
[docs] def on_message(self): """Calls on ASTM message receiving."""
def _get_state(self): return self._state def _set_state(self, value): assert value in STATE self._state = value #: ASTM handler state value: #: #: - ``init``: Neutral state #: - ``opened``: ENQ message was sent, waiting for ACK #: - ``transfer``: Data transfer processing #: state = property(_get_state, _set_state)
[docs] def set_init_state(self): """Sets handler state to INIT (0). In ASTM specification this state also called as `neutral` which means that handler is ready to establish data transfer. """ self.terminator = 1 self.state = STATE.init self.on_init_state() log.info('Switched to init state')
[docs] def set_opened_state(self): """Sets handler state to OPENED (1). Intermediate state that only means for client implementation. On this state client had already sent <ENQ> and awaits for <ACK> or <NAK> response. On <ACK> it switched his state to `transfer`. """ self.terminator = 1 self.state = STATE.opened self.on_opened_state() log.info('Switched to opened state')
[docs] def set_transfer_state(self): """Sets handler state to TRANSFER (2). In this state handler is able to send or receive ASTM messages depending on his role (client or server). At the end of data transfer client should send <EOT> and switch state to `init`. """ self.terminator = [CRLF, EOT] self.state = STATE.transfer self.on_transfer_state() log.info('Switched to transfer state')
[docs] def on_init_state(self): """Calls on set state INIT (0)"""
[docs] def on_opened_state(self): """Calls on set state OPENED (1)"""
[docs] def on_transfer_state(self): """Calls on set state TRANSFER (2)"""
[docs] def on_timeout(self): """Calls when timeout event occurs. Used to limit time for waiting response data."""

Project Versions